Hibernate many to many collection insertion optimization - java

I am new to working with databases and I have a Spring DATA JPA training project with HSQLDB that has 2 entities with child collections and many to many relationships.
One entity is called a Menu, and it contains a Dish list, and vice versa, a Dish contains a set of Menus to which it belongs.
When I try to insert a collection of dishes into a table, the Hibernate sends a separate call for each entity. Is there any way to optimize for Hibernate to send one complex query to save the entire child collection?
UPD.
Sorry for the mistake, actually I'm saving the Menu that contains the Dish collection, and not just saving the Dish collection.
Hibernate:
call next value for global_seq
Hibernate:
/* insert com.atanava.restaurants.model.Menu
*/ insert
into
menus
(date, restaurant_id, id)
values
(?, ?, ?)
Hibernate:
/* insert collection
row com.atanava.restaurants.model.Menu.dishes */ insert
into
dishes_menus
(menu_id, dish_id)
values
(?, ?)
Hibernate:
/* insert collection
row com.atanava.restaurants.model.Menu.dishes */ insert
into
dishes_menus
(menu_id, dish_id)
values
(?, ?)
Hibernate:
/* insert collection
row com.atanava.restaurants.model.Menu.dishes */ insert
into
dishes_menus
(menu_id, dish_id)
values
(?, ?)
Hibernate:
/* insert collection
row com.atanava.restaurants.model.Menu.dishes */ insert
into
dishes_menus
(menu_id, dish_id)
values
(?, ?)
Hibernate:
/* insert collection
row com.atanava.restaurants.model.Menu.dishes */ insert
into
dishes_menus
(menu_id, dish_id)
values
(?, ?)
Menu class:
#NamedQueries({
#NamedQuery(name = Menu.GET, query = "SELECT m FROM Menu m WHERE m.id=:id AND m.restaurant.id=:restaurantId"),
#NamedQuery(name = Menu.BY_RESTAURANT, query = "SELECT m FROM Menu m WHERE m.restaurant.id=:restaurantId ORDER BY m.date DESC"),
#NamedQuery(name = Menu.BY_DATE, query = "SELECT m FROM Menu m WHERE m.date=:date"),
#NamedQuery(name = Menu.BY_REST_AND_DATE, query = "SELECT m FROM Menu m WHERE m.restaurant.id=:restaurantId AND m.date=:date"),
#NamedQuery(name = Menu.DELETE, query = "DELETE FROM Menu m WHERE m.id=:id AND m.restaurant.id=:restaurantId"),
})
#Entity
#Table(name = "menus", uniqueConstraints = {#UniqueConstraint(columnNames = {"restaurant_id", "date"},
name = "restaurant_id_date_idx")})
public class Menu extends AbstractBaseEntity {
public static final String GET = "Menu.get";
public static final String BY_RESTAURANT = "Menu.getAllByRestaurant";
public static final String BY_DATE = "Menu.getAllByDate";
public static final String BY_REST_AND_DATE = "Menu.getByRestAndDate";
public static final String DELETE = "Menu.delete";
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "restaurant_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#NotNull
private Restaurant restaurant;
#ManyToMany(fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
#JoinTable(name = "dishes_menus",
joinColumns = #JoinColumn(name = "menu_id"),
inverseJoinColumns = #JoinColumn(name = "dish_id"))
private List<Dish> dishes;
#Column(name = "date", columnDefinition = "date default current_date", nullable = false)
#NotNull
private LocalDate date;
//constructors, getters, setters
}
Dish class:
#NamedQueries({
#NamedQuery(name = Dish.BY_RESTAURANT, query = "SELECT d FROM Dish d WHERE d.restaurant.id=:restaurantId ORDER BY d.name"),
})
#Entity
#Table(name = "dishes", uniqueConstraints = {#UniqueConstraint(columnNames = {"restaurant_id", "name"},
name = "unique_restaurant_id_dish_name_idx")})
public class Dish extends AbstractNamedEntity {
public static final String BY_RESTAURANT = "Dish.getAllByRestaurant";
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "restaurant_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#NotNull
private Restaurant restaurant;
#Column(name = "price", nullable = false)
#NotNull
private Integer price;
#Column(name = "active", nullable = false, columnDefinition = "bool default true")
private boolean active = true;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "dishes")
Set<Menu> menus;
//constructors, getters, setters
}
Menu repository where I haven't overridden save method yet:
I save menu with original method
crudMenuRepository.save(menu)
#Transactional(readOnly = true)
public interface CrudMenuRepository extends JpaRepository<Menu, Integer> {
#Transactional
#Modifying
#Query(name = Menu.DELETE)
int delete(#Param("id") int id, #Param("restaurantId") int restaurantId);
#Query(name = Menu.GET)
Menu get(#Param("id") int id, #Param("restaurantId") int restaurantId);
#Query(name = Menu.BY_REST_AND_DATE)
Menu getByRestAndDate(#Param("restaurantId") int restaurantId, #Param("date") LocalDate date);
#Query(name = Menu.BY_RESTAURANT)
List<Menu> getAllByRestaurant(#Param("restaurantId") int restaurantId);
#Query(name = Menu.BY_DATE)
List<Menu> getAllByDate(#Param("date") LocalDate date);
}
Snippet of initDB.sql file:
CREATE TABLE dishes
(
id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY,
restaurant_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
price INTEGER NOT NULL,
active BOOLEAN DEFAULT TRUE NOT NULL,
FOREIGN KEY (restaurant_id) REFERENCES restaurants (id) ON DELETE CASCADE
);
CREATE UNIQUE INDEX unique_restaurant_id_dish_name_idx on dishes (restaurant_id, name);
CREATE TABLE menus
(
id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY,
restaurant_id INTEGER NOT NULL,
date DATE DEFAULT CURRENT_DATE NOT NULL,
CONSTRAINT restaurant_id_date_idx UNIQUE (restaurant_id, date),
FOREIGN KEY (restaurant_id) REFERENCES restaurants (id) ON DELETE CASCADE
);
CREATE TABLE dishes_menus
(
dish_id INTEGER NOT NULL,
menu_id INTEGER NOT NULL,
CONSTRAINT dish_id_menu_id_idx UNIQUE (dish_id, menu_id),
FOREIGN KEY (dish_id) REFERENCES dishes (id) ON DELETE CASCADE,
FOREIGN KEY (menu_id) REFERENCES menus (id) ON DELETE CASCADE
);
UPD2
Here is my spring-db.xml config:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<jdbc:initialize-database data-source="dataSource" enabled="${database.init}">
<jdbc:script location="${jdbc.initLocation}"/>
<jdbc:script encoding="utf-8" location="classpath:db/populateDB.sql"/>
</jdbc:initialize-database>
<context:property-placeholder location="classpath:db/hsqldb.properties" system-properties-mode="OVERRIDE"/>
<!--no pooling-->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
p:driverClassName="org.hsqldb.jdbcDriver"
p:url="${database.url}"
p:username="${database.username}"
p:password="${database.password}"/>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="dataSource"
p:packagesToScan="com.atanava.**.model">
<!--p:persistenceUnitName="persistenceUnit">-->
<property name="jpaPropertyMap">
<map>
<entry key="#{T(org.hibernate.cfg.AvailableSettings).FORMAT_SQL}" value="${hibernate.format_sql}"/>
<entry key="#{T(org.hibernate.cfg.AvailableSettings).USE_SQL_COMMENTS}" value="${hibernate.use_sql_comments}"/>
<entry key="#{T(org.hibernate.cfg.AvailableSettings).JPA_PROXY_COMPLIANCE}" value="false"/>
<!--<entry key="#{T(org.hibernate.cfg.AvailableSettings).HBM2DDL_AUTO}" value="${hibernate.hbm2ddl.auto}"/>-->
</map>
</property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" p:showSql="${jpa.showSql}">
</bean>
</property>
</bean>
<tx:annotation-driven/>
<!-- Transaction manager for a single JPA EntityManagerFactory (alternative to JTA) -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory"/>
<context:component-scan base-package="com.atanava.**.repository**"/>
<jpa:repositories base-package="com.atanava.**.repository**"/>
</beans>
And here is a persistence.xml file that I created:
<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="menu-persistence-unit">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>com.atanava.restaurants.model.Menu</class>
<class>com.atanava.restaurants.model.Dish</class>
<properties>
<property name="hibernate.jdbc.batch_size" value="20"/>
</properties>
</persistence-unit>
</persistence>

You can find information about batching in the documentation: https://docs.jboss.org/hibernate/stable/orm/userguide/html_single/Hibernate_User_Guide.html#batch
Essentially, you just have to set the hibernate.jdbc.batch_size property in the persistence.xml to e.g. 20.

Related

Hibernate left join fetch to soft-deleted entity mapped with #ManyToMany association generates invalid query?

I don't know if this is a problem with Hiberante or if I'm doing something wrong.
I tried to make this example as simple as possible.
My entities:
Book.java:
#Entity
#Table(name="books")
#SQLDelete(sql="UPDATE books set is_deleted = true where id = ?")
#Where(clause="is_deleted = false")
public class Book implements Serializable {
#Id
#Column(name = "id", updatable = false, nullable = false)
private long id;
#Column (name="is_deleted")
private boolean deleted;
#Column(name="title")
private String title;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(
name="books_authors",
joinColumns = {
#JoinColumn(name="id_books")
},
inverseJoinColumns = {
#JoinColumn(name="id_authors")
}
)
private Set<Author> authors = new HashSet<>();
//getters/setters
}
Author.java:
#Entity
#Table(name="autors")
#SQLDelete(sql="UPDATE autors set is_deleted = true where id = ?")
#Where(clause="is_deleted = false")
public class Author implements Serializable {
#Id
#Column(name = "id", updatable = false, nullable = false)
private long id;
#Column(name="name")
private String name;
#Column (name="is_deleted")
private boolean deleted;
#ManyToMany(mappedBy = "authors", fetch = FetchType.LAZY)
private Set<Book> books = new HashSet<>();
//getters/setters
}
underlying tables:
create table autors (
id int8 not null,
is_deleted boolean,
name varchar(255),
primary key (id)
);
create table books (
id int8 not null,
is_deleted boolean,
title varchar(255),
primary key (id)
);
create table books_authors (
id_books int8 not null,
id_authors int8 not null,
primary key (id_books, id_authors)
);
alter table if exists books_authors add constraint FK9xru8ocoxufr5eva6atfyfjxh foreign key (id_authors) references autors;
alter table if exists books_authors add constraint FKx5d5025t9ct5kcbwwxm6tipo foreign key (id_books) references books;
Data:
select * from books;
id
is_deleted
title
1
false
TITLE_1
2
false
TITLE_2
select * from authors;
id
is_deleted
name
1
false
not deleted
2
true
DELETED!
select * from books b
join books_authors ba on b.id = ba.id_books
join autors a on a.id = ba.id_authors;
id
is_deleted
title
id_books
id_authors
id
is_deleted
name
1
false
TITLE_1
1
1
1
false
not deleted
2
false
TITLE_2
2
2
2
true
DELETED!
JPQL:
public Optional<Book> getBookWithSoftDeletedAuthor(long bookId) {
List<Book> myBooks = em.createQuery(
"select b from " +
"Book b " +
"left join fetch b.authors a " +
"where b.id = :bookId", Book.class)
.setParameter("bookId", bookId)
.getResultList();
return myBooks.size() > 0 ? Optional.of(myBooks.get(0)) : Optional.empty();
}
persistence.xml:
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="WildflyUnit">
<description> Persistence unit for Wildfly </description>
<jta-data-source>java:jboss/datasources/WildflyDataSource</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQL95Dialect"/>
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.generate_statistics" value="true" />
</properties>
</persistence-unit>
</persistence>
datasource from Wildfly's standalone.xml:
<datasource jndi-name="java:jboss/datasources/WildflyDataSource" pool-name="WildflyDataSource" enabled="true" use-java-context="true" statistics-enabled="true">
<connection-url>jdbc:postgresql://localhost:5432/TestDB</connection-url>
<driver>postgresql</driver>
<security>
<user-name>wildfly_user</user-name>
<password>********</password>
</security>
</datasource>
Hibernate generates sql statement like this:
select
book0_.id as id1_1_0_,
author2_.id as id1_0_1_,
book0_.is_deleted as is_delet2_1_0_,
book0_.title as title3_1_0_,
author2_.is_deleted as is_delet2_0_1_,
author2_.name as name3_0_1_,
authors1_.id_books as id_books1_2_0__,
authors1_.id_authors as id_autho2_2_0__ -- this column is not null even if the author is soft-deleted
from
books book0_
left outer join
books_authors authors1_
on book0_.id=authors1_.id_books
left outer join
authors author2_
on authors1_.id_authors=author2_.id
and (
author2_.is_deleted = false
)
where
(
book0_.is_deleted = false
)
and book0_.id=2
imho Hibernate should generate this query like this:
select
book0_.id as id1_1_0_,
author2_.id as id1_0_1_,
book0_.is_deleted as is_delet2_1_0_,
book0_.title as title3_1_0_,
author2_.is_deleted as is_delet2_0_1_,
author2_.name as name3_0_1_,
book0_.id as id_books1_2_0__,
author2_.id as id_autho2_2_0__
from
books book0_
left outer join
books_authors authors1_
on book0_.id=authors1_.id_books
left outer join
authors author2_
on authors1_.id_authors=author2_.id
and (
author2_.is_deleted = false
)
where
(
book0_.is_deleted = false
)
and book0_.id=2
the id_autho2_2_0__ column should come from author2_ (authors table), not from authors1_ (books_authors table). Since the value of the authors1_.id_authors column is not null, hibernate tries to fetch a soft deleted entity from the authors table.
I use Hibernate 5.4.27.Final

Problems with java #GeneratedValue (strategy = GenerationType.IDENTITY) using MariaDB 10.4 and eclipselink

I am developing a REST web service in Java EE I am using: Glassfish 5.0 (build 25), MariaDB 10.4 and eclipselink (JPA 2.1)
here is my code:
commande_line table
CREATE TABLE IF NOT EXISTS `cooldb`.`commande_line` (
`id` INT NOT NULL AUTO_INCREMENT,
`quantity` INT NULL,
`discount` INT NULL,
`dish` INT NOT NULL,
`commande` INT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC),
INDEX `fk_commande_line_dish1_idx` (`dish` ASC),
INDEX `fk_commande_line_commande1_idx` (`commande` ASC),
CONSTRAINT `fk_commande_line_dish1`
FOREIGN KEY (`dish`)
REFERENCES `cooldb`.`dish` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `fk_commande_line_commande1`
FOREIGN KEY (`commande`)
REFERENCES `cooldb`.`commande` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
persistance.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<!-- Define Persistence Unit -->
<persistence-unit name="my_persistence_unit" transaction-type="JTA">
<jta-data-source>jdbc/mariadb</jta-data-source>
<class>com.yac.model.Address</class>
<class>com.yac.model.Commande</class>
<class>com.yac.model.CommandeLine</class>
<class>com.yac.model.Dish</class>
<class>com.yac.model.Dishtype</class>
<class>com.yac.model.Ingredient</class>
<class>com.yac.model.Payement</class>
<class>com.yac.model.Profil</class>
<class>com.yac.model.Restaurant</class>
<class>com.yac.model.Userapp</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
</properties>
</persistence-unit>
</persistence>
commandeline entity
public class CommandeLine implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Column(name = "quantity")
private Integer quantity;
#Column(name = "discount")
private Integer discount;
#JoinColumn(name = "commande", referencedColumnName = "id")
#ManyToOne(optional = false)
private Commande commande;
#JoinColumn(name = "dish", referencedColumnName = "id")
#ManyToOne(optional = false)
private Dish dish;
//Constructor
// Setter and Getter
}
commandeline web service
#Stateless
#Path("commandeline")
public class CommandeLineFacadeREST extends AbstractFacade<CommandeLine> {
#PersistenceContext(unitName = "my_persistence_unit")
private EntityManager em;
public CommandeLineFacadeREST() {
super(CommandeLine.class);
}
#POST
#Override
#Consumes(MediaType.APPLICATION_JSON)
public void create(CommandeLine entity) {
super.create(entity);
}
#Override
protected EntityManager getEntityManager() {
return em;
}
}
AbstractFacade
public abstract class AbstractFacade<T> {
private Class<T> entityClass;
public AbstractFacade(Class<T> entityClass) {
this.entityClass = entityClass;
}
protected abstract EntityManager getEntityManager();
public void create(T entity) {
getEntityManager().persist(entity);
}
}
The problem is when I test my web service with Postman and I try to insert a record with a POST request
here is what I receive as error message:
Local Exception Stack:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.7.0.v20170811-d680af5): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLSyntaxErrorException: (conn=158) Table 'cooldb.sequence' doesn't exist
Error Code: 1146
Call: UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?
bind => [2 parameters bound]
Query: DataModifyQuery(name="SEQ_GEN_SEQUENCE" sql="UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?") ...
I don't understand why the problem with SEQUANCE when I use #GeneratedValue (strategy = GenerationType.IDENTITY).
When I change with #GeneratedValue (strategy = GenerationType.SEQUENCE) and I create the table with the following script:
CREATE SEQUENCE SEQUANCE START WITH 1 INCREMENT BY 1;
by applying the solution shown in : Table 'customerjpa.sequence' doesn't exist JPA
but the same probleme
thank you in advance for your help.
The problem is solved using Chris comments, i just add the following line in my persistence.xml file:
<property name="eclipselink.target-database" value="MySQL"/>
Thank you very much Chris.
So my new persistence.xml file is:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<!-- Define Persistence Unit -->
<persistence-unit name="my_persistence_unit" transaction-type="JTA">
<jta-data-source>jdbc/mariadb</jta-data-source>
<class>com.yac.model.Address</class>
<class>com.yac.model.Commande</class>
<class>com.yac.model.CommandeLine</class>
<class>com.yac.model.Dish</class>
<class>com.yac.model.Dishtype</class>
<class>com.yac.model.Ingredient</class>
<class>com.yac.model.Payement</class>
<class>com.yac.model.Profil</class>
<class>com.yac.model.Restaurant</class>
<class>com.yac.model.Userapp</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="eclipselink.target-database" value="MySQL"/>
</properties>
</persistence-unit>
</persistence>
I just specified the database platform in MySQL in the persistence.xml file of the moment MariaDB is based on it, because MariaDB is not mentioned in the list.
If there are other suggestions do not hesitate thank you.
Another Solution:
Add ?useMysqlMetadata=true to your JDBC URL connection as bellow:
<property name="URL" value="jdbc:mariadb://[HOST]:[PORT]/[DB NAME]?useMysqlMetadata=true"/>
that will make MariaDB use MySQL meta data and then eclipselink will detect it as MySQL.

JPA/Hibernate double select on OneToOne Find

While learning JPA/Hibernate I stumbbled upon something unexpected. I am getting an unnecessary double select query to the db (see buttom).I have a simple OneToOne setup.
This is my Contact entity:
#Entity
#Table(name = "contact")
public class Contact {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "contact_id", nullable = false, insertable = false, updatable = false)
private Long id;
#Column(name = "name", nullable = false)
private String name;
#OneToOne
#JoinColumn(name="fk_address_id", referencedColumnName="address_id")
private Address address;
public Contact(String name, Address address) {
this.name = name;
this.address = address;
}
// getters/setters
}
My Address entity:
#Entity
#Table(name = "address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "address_id", nullable = false, insertable = false, updatable = false)
private Long id;
#Column(name = "location", nullable = false)
private String location;
#OneToOne(mappedBy = "address")
private Contact contact;
public Address(String location) {
this.location = location;
}
// getters/setters
}
This is how I ran my code:
private EntityManager em;
#Before
public void setup() {
em = EntityManagerFactoryCreator.getEntityManagerFactory().createEntityManager();
}
#Test
public void createContactWithAddress() {
Address address = new Address("Made");
Contact contact = new Contact("Jan", address);
em.getTransaction().begin();
em.persist(address);
em.persist(contact);
em.getTransaction().commit();
em.close();
em = EntityManagerFactoryCreator.getEntityManagerFactory().createEntityManager();
Contact managedContact = em.find(Contact.class, 1L);
}
}
It is probably something stupid, but what is causing the double select?
Hibernate:
drop table address if exists
Hibernate:
drop table book if exists
Hibernate:
drop table contact if exists
Hibernate:
create table address (
address_id bigint generated by default as identity,
location varchar(255) not null,
primary key (address_id)
)
Hibernate:
create table book (
book_id bigint generated by default as identity,
category varchar(255),
release_date date,
summary varchar(255),
title varchar(255) not null,
primary key (book_id)
)
Hibernate:
create table contact (
contact_id bigint generated by default as identity,
name varchar(255) not null,
address_address_id bigint,
primary key (contact_id)
)
Hibernate:
alter table book
add constraint UK_g0286ag1dlt4473st1ugemd0m unique (title)
Hibernate:
alter table contact
add constraint FKrc0ixa9b11b9tv3hyq0iwvdpt
foreign key (address_address_id)
references address
Hibernate:
insert
into
address
(address_id, location)
values
(null, ?)
TRACE o.h.t.d.s.BasicBinder [main]: binding parameter [1] as [VARCHAR] - [Made]
Hibernate:
insert
into
contact
(contact_id, address_address_id, name)
values
(null, ?, ?)
TRACE o.h.t.d.s.BasicBinder [main]: binding parameter [1] as [BIGINT] - [1]
TRACE o.h.t.d.s.BasicBinder [main]: binding parameter [2] as [VARCHAR] - [Jan]
Hibernate:
select
contact0_.contact_id as contact_1_2_0_,
contact0_.address_address_id as address_3_2_0_,
contact0_.name as name2_2_0_,
address1_.address_id as address_1_0_1_,
address1_.location as location2_0_1_
from
contact contact0_
left outer join
address address1_
on contact0_.address_address_id=address1_.address_id
where
contact0_.contact_id=?
TRACE o.h.t.d.s.BasicBinder [main]: binding parameter [1] as [BIGINT] - [1]
TRACE o.h.t.d.s.BasicExtractor [main]: extracted value ([address_1_0_1_] : [BIGINT]) - [1]
TRACE o.h.t.d.s.BasicExtractor [main]: extracted value ([address_3_2_0_] : [BIGINT]) - [1]
TRACE o.h.t.d.s.BasicExtractor [main]: extracted value ([name2_2_0_] : [VARCHAR]) - [Jan]
TRACE o.h.t.d.s.BasicExtractor [main]: extracted value ([location2_0_1_] : [VARCHAR]) - [Made]
Hibernate:
select
contact0_.contact_id as contact_1_2_1_,
contact0_.address_address_id as address_3_2_1_,
contact0_.name as name2_2_1_,
address1_.address_id as address_1_0_0_,
address1_.location as location2_0_0_
from
contact contact0_
left outer join
address address1_
on contact0_.address_address_id=address1_.address_id
where
contact0_.address_address_id=?
TRACE o.h.t.d.s.BasicBinder [main]: binding parameter [1] as [BIGINT] - [1]
TRACE o.h.t.d.s.BasicExtractor [main]: extracted value ([address_1_0_0_] : [BIGINT]) - [1]
TRACE o.h.t.d.s.BasicExtractor [main]: extracted value ([contact_1_2_1_] : [BIGINT]) - [1]
Note:
My EntityManagerFactoryCreator is a singleton that calls
Persistence.createEntityManagerFactory("nl.infosupport.javaminor.week4.jpa.h2");
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="nl.infosupport.javaminor.week4.jpa.h2">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value="sa"/>
<property name="javax.persistence.jdbc.url" value="jdbc:h2:~/Documents/InfoSupport-Minor/h2_embedded_db/test"/>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>
This is normal behavior in this case and with the tables config you have. If you want to have just one select, you need to use a custom query joining the child table. For example, using HQL it would look like that:
public Contact find(Long id) {
TypedQuery<Contact> query = em.createQuery(
"SELECT c FROM Contact c join fetch c.address WHERE c.id = :id", Contact.class);
return query
.setParameter("id", id)
.getSingleResult();
}
The code is not necessary working, I didn't debug it, but it shows the principle.
UPDATED
Or you can try annotating the field like
#Fetch(FetchMode.JOIN)
private Address address;
And then it will fire just one query.

One to one relationship select query fetches data once first time only

I'm trying to apply one to one relationship between two entities
first entity:
Video and OrganzationVideo every OrganizationVideo has one video entity
So I did the following
first organization_video table
CREATE TABLE `organization_video` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
video table
CREATE TABLE `video` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(100) DEFAULT NULL,
// rest of table contents
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
then I added constraint in organization_video table
CONSTRAINT `FK_organization_video` FOREIGN KEY (`id`) REFERENCES `video` (`id`)
Then generated entities
Video.java
#Entity
#Table(name = "video")
public class Video extends Persistable<Long> {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "video")
private OrganizationVideo organizationVideo;
\\ rest of video contetns
}
OrganizationVideo.java
#Entity
#Table(name = "organization_video")
public class OrganizationVideo extends Persistable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Long id;
#JoinColumn(name = "id", referencedColumnName = "id")
#OneToOne(optional = false)
private Video video;
\\ rest of organziation video contents
}
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="Default_Persistence_Unit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<non-jta-data-source>jdbc/defaultDataSource</non-jta-data-source>
<properties>
<property name="hibernate.transaction.auto_close_session" value="false"/>
<property name="hibernate.max_fetch_depth" value="0"/>
<property name="hibernate.connection.release_mode" value="auto"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.cglib.use_reflection_optimizer" value="true"/>
</properties>
</persistence-unit>
</persistence>
everything worked perfectly when persisting objects
the issue is with the fetching query
StringBuilder queryStr = new StringBuilder("select v from Video v where v.organizationVideo is null");
Query query = getEntityManager().createQuery(queryStr.toString());
return query.getResultList();
The weird behavior is that this query fetches data once then doesn't fetch any data tried to use #PrimaryKeyJoinColumn and changed id generation type with no luck but indeed there is something wrong it's strange to see data once first time when I tried to remove is null from query it fetches data but with null value assigned to OrganizationVideo then why the query doesn't work except at first time.
You will have to check the SQL that is generated for the query, but I'm guessing that since the ID is the foreign key and it is autogenerated, inserting entities causes problems with your query. Try fixing your model first:
Either the OrganizationVideo ID needs to be set by the video relationship (if using JPA 2.0):
#Entity
#Table(name = "organization_video")
public class OrganizationVideo extends Persistable<Long> {
#Id
#JoinColumn(name = "id", referencedColumnName = "id")
#OneToOne(optional = false)
private Video video;
\\ rest of organziation video contents
}
That, or you make one of the ID mappings within OrganizationVideo insertable=false, updatable=false. if you do this, then you must set the other fields with in OrganizationVideo within the application yourself, and only after the Video instance has an ID value assigned. Since you are using identity, this means you would have to persist the vidoe, flush, and then use that value within the OrganizationVideo before it can be persisted.
It would be easier for your model if you could just add a foreign key to the OrganizationVideo table and use that to reference the Video ID.

cannot accept a NULL value on #ID #generatedvalue JPA entity

I am trying to make a add to wish list feature for my app but I keep getting this error:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: Column 'WISH_ID' cannot accept a NULL value.
Error Code: -1
Call: INSERT INTO WISHLIST (BOOK_TITLE, CUSTOMER_ID) VALUES (?, ?)
bind => [2 parameters bound]
Query: InsertObjectQuery(dukesbookstore.entity.Wishlist[ wishId=null ])
ENTITY class:
#Entity
#Table(name = "WISHLIST")
#XmlRootElement
#NamedQueries(
{
#NamedQuery(name = "Wishlist.findByCustomerId", query = "SELECT w FROM Wishlist w WHERE w.customerId = :customerId"),
})
public class Wishlist implements Serializable
{
private static final long serialVersionUID = 1L;
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "WISH_ID")
private Integer wishId;
#Column(name = "CUSTOMER_ID")
private Integer customerId;
#Size(max = 35)
#Column(name = "BOOK_TITLE")
private String bookTitle;
public Wishlist()
{
}
public Integer getCustomerId()
{
return customerId;
}
public void setCustomerId(Integer customerId)
{
this.customerId = customerId;
}
public String getBookTitle()
{
return bookTitle;
}
public void setBookTitle(String bookTitle)
{
this.bookTitle = bookTitle;
}
}
and this is the code for creating a new wish:
public void createWishlist(String title,int cust_id)
{
Wishlist newWish = new Wishlist();
newWish.setBookTitle(title);
newWish.setCustomerId(cust_id);
em.persist(newWish);
}
I tried to look at other similar problems but they involves hibernate which i am not using. I have also tried various generation strategy such as AUTO,SEQUENCE,TABLE but all failed. I also have another entity named customer which is exactly same but it works fine though its created from a form.
Changing to AUTO generates this error:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLSyntaxErrorException: Table/View 'SEQUENCE' does not exist.
Error Code: -1
Call: UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?
bind => [2 parameters bound]
Query: DataModifyQuery(name="SEQUENCE" sql="UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?")
root cause
java.sql.SQLSyntaxErrorException: Table/View 'SEQUENCE' does not exist.
root cause
org.apache.derby.client.am.SqlException: Table/View 'SEQUENCE' does not exist.
Persistence.xml incase relevant
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="myStorePU" transaction-type="JTA">
<jta-data-source>mydb</jta-data-source>
<class>myStore.entity.Book</class>
<class>myStore.entity.Customer</class>
<class>myStore.entity.Wishlist</class>
<properties>
<property name="javax.persistence.jdbc.user" value="APP"/>
<property name="javax.persistence.jdbc.password" value="APP"/>
<property name="eclipselink.logging.level" value="FINE"/>
</properties>
</persistence-unit>
Finally it is working now,
firstly, i had to delete the table that I created from netbeans db creation tool, which had a creation code like this, as seen by using "grab structure"
create table "APP".WISHLIST
(
WISH_ID NUMERIC(5) not null primary key,
CUSTOMER_ID NUMERIC(5),
BOOK_TITLE VARCHAR(100)
)
secondly, I added this code into my persistence.xml file
<property name="eclipselink.ddl-generation" value="create-tables"/>
This solved the problem as it created the table by it self, which have different creation code, as seen from its grab structure from auto creation:
create table "APP".WISHLIST
(
WISH_ID INTEGER default GENERATED_BY_DEFAULT not null primary key,
CUSTOMER_ID NUMERIC(5),
BOOK_TITLE VARCHAR(100)
)
So, basically should let netbeans create the table itself from entity but i was using "Create entity from table" features, for that i had to create the tables first in netbeans gui.
Thank you #Geziefer for all the help, I learned quite a bit from your help too.
This might be a "Non nullable attributes" in JPA single-table-inheritance problem. It might help and doesn't hurt to specify
#JoinTable(name = "[some join table name]",
joinColumns = {#JoinColumn(name = "[some ID column name]")},
inverseJoinColumns = {#JoinColumn(name = "[some different ID column name]")})

Categories

Resources