I am learning Java EE and for 2 days already I've been struggling with configuration of Hibernate to work with MySQL database on a TomEE server in a simple Java EE web application.
I have 2 simple entities: Car and Seat, with the uni-directional @OneToMany relation from Car to Seat(s). Color and EngineType are plain enums, while Specification is a value object for these 2 enums). NOTE: FetchType.EAGER is used in the @OneToMany for learning purposes, I am familiar that this is not a good solution normally.
When I try to configure the persistence.xml file, alghouth specifying everything "as for MySQL", it seems that Hibernate still uses the default HSQLDB syntax/dialect/engine and as a result, I recieve errors during schema creation:
[INFO] TomEE embedded started on localhost:8080
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
Hibernate: alter table Seat drop foreign key FKkkm9pdx9e1t9jva76n9tqhhqv
mar 06, 2020 1:48:31 PM org.hibernate.tool.schema.internal.ExceptionHandlerLoggedImpl handleException
WARN: GenerationTarget encountered exception accepting command : Error executing DDL "alter table Seat drop foreign key FKkkm9pdx9e1t9jva76n9tqhhqv" via JDBC Statement
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "alter table Seat drop foreign key FKkkm9pdx9e1t9jva76n9tqhhqv" via JDBC Statement
(...)
Caused by: java.sql.SQLSyntaxErrorException: user lacks privilege or object not found: PUBLIC.SEAT
(...)
Caused by: org.hsqldb.HsqlException: user lacks privilege or object not found: PUBLIC.SEAT // what is going on here???
and for later Hibernate SQL commands while creating DDL, I have also:
Hibernate: create table Car (identifier bigint not null auto_increment, color varchar(255), engineType varchar(255), primary key (identifier)) engine=InnoDB
mar 06, 2020 1:48:31 PM org.hibernate.tool.schema.internal.ExceptionHandlerLoggedImpl handleException
WARN: GenerationTarget encountered exception accepting command : Error executing DDL "create table Car (identifier bigint not null auto_increment, color varchar(255), engineType varchar(255), primary key (identifier)) engine=InnoDB" via JDBC Statement
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table Car (identifier bigint not null auto_increment, color varchar(255), engineType varchar(255), primary key (identifier)) engine=InnoDB" via JDBC Statement
(...)
Caused by: java.sql.SQLSyntaxErrorException: unexpected token: AUTO_INCREMENT
(...)
Caused by: org.hsqldb.HsqlException: unexpected token: AUTO_INCREMENT // definitely something messed up, this is a correct MySQL token
When I omit all "MySQL stuff" (driver and dialect), and make Hibernate use default HSQLDB, it works fine... The DDL for HSQLDB is created well.
I tried many different configurations and replacements googling it over the web and SO, but not found any hint why Hibernate still uses non-MySQL syntax (but I am not 100% sure that this is the direct cause of the issue).
I attempted to specify the datasource in resources.xml file under webapp, but it doesn't change anything. Additionally, I checked if HSQLDB could be excluded from Maven build, but it ships with hibernate-core, so it's not easily achievable and perhaps could also not help.
I have previously used Hibernate with Spring and SpringBoot, but with Java EE I am totally lost here, that's why I asked the question.
Could anyone help with the issue and give some advices on the correct configuration of all this?
My persistence.xml under resources/META-INF looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.2"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="my-persistence-unit" transaction-type="JTA">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>java:openejb/Resource/myJtaDatabase</jta-data-source>
<!-- Entity classes -->
<class>com.example.javaeecourse.entity.Car</class>
<class>com.example.javaeecourse.entity.Seat</class>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/javaeecourse?serverTimezone=UTC" />
<property name="hibernate.connection.username" value="root" />
<property name="hibernate.connection.password" value="admin" />
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL8Dialect" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="create"/>
<property name="tomee.jpa.factory.lazy" value="true" />
<!--<property name="tomee.jpa.cdi=false" value="false" />-->
</properties>
</persistence-unit>
</persistence>
Car entity:
package com.example.javaeecourse.entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
import static com.example.javaeecourse.entity.Car.FIND_ALL;
@Getter
@Setter
@Entity
@Table(name = "cars")
@NamedQuery(name = FIND_ALL, query = "SELECT car FROM Car car")
@NoArgsConstructor
public class Car {
public static final String FIND_ALL = "Car.findAll";
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long identifier;
@Enumerated(EnumType.STRING)
private Color color;
@Enumerated(EnumType.STRING)
private EngineType engineType;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "car", nullable = false)
private Set<Seat> seats = new HashSet<>();
}
Seat entity:
package com.example.javaeecourse.entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Table(name = "seats")
@NoArgsConstructor
@Getter
@Setter
public class Seat {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String seatMaterial;
public Seat(String seatMaterial) {
this.seatMaterial = seatMaterial;
}
}
CarManufacturer class (@Stateless EJB, where EntityManager is called):
package com.example.javaeecourse.boundary;
import com.example.javaeecourse.control.CarFactory;
import com.example.javaeecourse.entity.Car;
import com.example.javaeecourse.entity.CarCreatedEvent;
import com.example.javaeecourse.entity.Specification;
import javax.ejb.Stateless;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
@Stateless
public class CarManufacturer {
@Inject
CarFactory carFactory;
@Inject
Event<CarCreatedEvent> carCreatedEvent;
@PersistenceContext
EntityManager entityManager;
public Car manufactureCar(Specification specification) {
Car car = carFactory.createCar(specification);
entityManager.persist(car);
carCreatedEvent.fire(new CarCreatedEvent(car.getIdentifier()));
return car;
}
public List<Car> retrieveCars() {
return entityManager.createNamedQuery(Car.FIND_ALL, Car.class).getResultList();
}
}
CarFactory class, which instantiates the entity:
package com.example.javaeecourse.control;
import com.example.javaeecourse.entity.*;
import javax.inject.Inject;
public class CarFactory {
@Inject
@DefaultCarColor
Color randomCarColor;
@Inject
@DefaultCarEngineType
EngineType randomCarEngineType;
public Car createCar(Specification specification) {
Car car = new Car();
car.setColor(specification.getColor() == null ? randomCarColor : specification.getColor());
car.setEngineType(specification.getEngineType() == null ? randomCarEngineType : specification.getEngineType());
Seat seat = new Seat("Leather");
car.getSeats().add(seat);
return car;
}
}
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>javaeecourse</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>Java EE Course App</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<failOnMissingWebXml>false</failOnMissingWebXml>
</properties>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.10.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.10.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<finalName>javaeecourse</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<groupId>org.apache.tomee.maven</groupId>
<artifactId>tomee-embedded-maven-plugin</artifactId>
<version>8.0.1</version>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
I will be grateful for any help.
Thanks to the hint provided by @areus in the question comment, I was able to resolve the case. I am posting an answer here if anyone had a similar issue in the future.
There was 1 major issue and 1 "bad practice" issue.
The main problem was that I was using a JTA transaction type and referenced the jta-data-source by JNDI name, but the resources.xml was located under webapp instead of resources/META-INF. After I moved the resource.xml to the META-INF, the database could be properly created and entities were persisted.
Additionally, in my configuration, I have used a deprecated JDBC Driver for MySQL 8. According to this doc, since MySQL 8.0, the driver is now com.mysql.cj.jdbc.Driver
, not com.mysql.jdbc.Driver
.
I also removed the unnecessary properties, so the config is now quite clear and everything works fine.
resources/META-INF/persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.2"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="my-persistence-unit" transaction-type="JTA">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>jdbc/javaeecourse</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL8Dialect"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
<property name="tomee.jpa.factory.lazy" value="true"/>
</properties>
</persistence-unit>
</persistence>
resources/META-INF/resources.xml
<?xml version="1.0" encoding="UTF-8"?>
<tomee>
<Resource id="jdbc/javaeecourse" type="javax.sql.DataSource">
JdbcDriver = com.mysql.cj.jdbc.Driver
JdbcUrl = jdbc:mysql://localhost:3306/javaeecourse?serverTimezone=UTC
UserName = root
Password = admin
jtaManaged = true
</Resource>
</tomee>
Here is also a great article regarding the JTA/RESOURCE_LOCAL transaction types as well as jta-data-source and non-jta-data-source configuration, which helped me understand the details.