Hello everyone.
I have a larger code base, which I’ve translated to the following mini example to hopefully be able to get some help on the concept(s) that I’m not understanding correctly.
In the mini example problem I made, I’m trying to have a Basket entity containing multiple Kiwis (a List of Kiwi objects) and a single Watermelon object. This would mean having a table for Kiwi with a foreign key UUID to Basket and also a table for Watermelon with a foreign key UUID to Basket. I’m using SQLite and the UUIDs are Strings, not the java.util.UUID (or any other) class, for what it’s worth.
Unless I’ve forgotten something, the following is all the relevant information.:
./src
./src/main
./src/main/java
./src/main/java/com
./src/main/java/com/mycompany
./src/main/java/com/mycompany/myapp
./src/main/java/com/mycompany/myapp/Basket.java
./src/main/java/com/mycompany/myapp/Driver.java
./src/main/java/com/mycompany/myapp/HibernateUtil.java
./src/main/java/com/mycompany/myapp/Kiwi.java
./src/main/java/com/mycompany/myapp/KiwiConverter.java
./src/main/java/com/mycompany/myapp/Watermelon.java
./src/main/java/com/mycompany/myapp/WatermelonConverter.java
./src/main/java/com/mycompany/myapp/BasketDao.java
./src/main/java/com/mycompany/myapp/StringListConverter.java
./src/main/resources
./src/main/resources/hibernate.cfg.xml
./src/test
./src/test/java
./src/test/java/com
./src/test/java/com/mycompany
./src/test/java/com/mycompany/myapp
BasketDao.java:
package com.mycompany.myapp;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.Transaction;
public class BasketDao {
private static BasketDao transactionSetDao = null;
private BasketDao() {}
public static BasketDao getInstance() {
if(transactionSetDao == null) {
transactionSetDao = new BasketDao();
}
return transactionSetDao;
}
public<T> void save(T data) {
Transaction tx = null;
try( Session session = HibernateUtil.getSessionFactory().openSession() ) {
tx = session.beginTransaction();
session.merge(data);
tx.commit();
}
}
public <T> List<T> loadAllData(Class<T> type) {
Transaction tx = null;
try( Session session = HibernateUtil.getSessionFactory().openSession() ) {
tx = session.beginTransaction();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<T> criteria = builder.createQuery(type);
criteria.from(type);
List<T> data = session.createQuery(criteria).getResultList();
tx.commit();
return data;
}
}
}
Basket.java:
package com.mycompany.myapp;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Entity
public class Basket implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(name="basketUuid")
private String basketUuid;
private String brandName;
@Convert(converter = WatermelonConverter.class)
@OneToOne(mappedBy="basket", cascade=CascadeType.ALL)
private Watermelon watermelon;
@Convert(converter = KiwiConverter.class)
@Convert(converter = StringListConverter.class)
@OneToMany(mappedBy="basket", cascade=CascadeType.ALL)
@ElementCollection
private List<Kiwi> kiwis;
public Basket() {
this(UUID.randomUUID().toString(), "Generic", new Watermelon(), new ArrayList<Kiwi>());
}
public Basket(String basketUuid, String brandName, Watermelon watermelon, List<Kiwi> kiwis) {
this.basketUuid = basketUuid;
this.brandName = brandName;
this.watermelon = watermelon;
this.kiwis = kiwis;
}
public String getBasketUuid() {
return basketUuid;
}
public void setBasketUuid(String basketUuid) {
this.basketUuid = basketUuid;
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
public Watermelon getWatermelon() {
return watermelon;
}
public void setWatermelon(Watermelon watermelon) {
this.watermelon = watermelon;
}
public List<Kiwi> getKiwis() {
return kiwis;
}
public void setKiwis(List<Kiwi> kiwis) {
this.kiwis = kiwis;
}
}
Driver.java:
package com.mycompany.myapp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
public class Driver {
public static void main(String[] args) throws Exception {
try/*( Session session = HibernateUtil.getSessionFactory().openSession() )*/ {
// tx = session.beginTransaction();
Watermelon watermelon = new Watermelon(10.0, LocalDateTime.now());
Kiwi kiwi1 = new Kiwi();
kiwi1.setPurchasePrice(1.0);
kiwi1.setPurchaseTimestamp(LocalDateTime.now());
Kiwi kiwi2 = new Kiwi();
kiwi2.setPurchasePrice(1.0);
kiwi2.setPurchaseTimestamp(LocalDateTime.now().plusMinutes(1));
List<Kiwi> kiwis = new ArrayList<>(2);
kiwis.add(kiwi1);
kiwis.add(kiwi2);
Basket basket = new Basket();
basket.setBrandName("Tote");
basket.setWatermelon(watermelon);
basket.setKiwis(kiwis);
// BasketDao.getInstance().save(watermelon);
// BasketDao.getInstance().save(kiwis);
BasketDao.getInstance().save(basket);
// tx.commit();
} catch (Exception e) {
// tx.rollback();
e.printStackTrace();
}
}
}
HibernateUtil.java:
package com.mycompany.myapp;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
public class HibernateUtil {
private static SessionFactory sessionFactory;
private static SessionFactory buildSessionFactory() {
StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder().
configure("hibernate.cfg.xml").build();
Metadata metadata = new MetadataSources(standardRegistry).getMetadataBuilder().
build();
SessionFactoryBuilder sessionFactoryBuilder = metadata.getSessionFactoryBuilder();
SessionFactory sessionFactory = sessionFactoryBuilder.build();
return sessionFactory;
}
public static SessionFactory getSessionFactory() {
if (sessionFactory == null) {
sessionFactory = buildSessionFactory();
}
return sessionFactory;
}
}
KiwiConverter.java:
package com.mycompany.myapp;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.time.LocalDateTime;
@Converter
public class KiwiConverter implements AttributeConverter<Kiwi,String> {
private static final String SEPARATOR = ";";
@Override
public String convertToDatabaseColumn(Kiwi kiwi) {
StringBuilder sb = new StringBuilder();
sb.append( kiwi.getKiwiUuid() );
sb.append( SEPARATOR );
sb.append( kiwi.getPurchasePrice());
sb.append( SEPARATOR );
sb.append( kiwi.getPurchaseTimestamp());
return sb.toString();
}
@Override
public Kiwi convertToEntityAttribute(String dbKiwi) {
String[] parts = dbKiwi.split(SEPARATOR);
Kiwi kiwi = new Kiwi();
int i = 0;
kiwi.setKiwiUuid( parts[i++] );
kiwi.setPurchasePrice( Double.parseDouble(parts[i++]) );
kiwi.setPurchaseTimestamp( LocalDateTime.parse(parts[i++]) );
/*Basket basket = new Basket(basketUuid, brandName, watermelon, kiwis);
kiwi.setBasket(basket);*/
return kiwi;
}
}
Kiwi.java:
package com.mycompany.myapp;
import jakarta.persistence.Entity;
import static jakarta.persistence.FetchType.LAZY;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import java.time.LocalDateTime;
import java.util.UUID;
@Entity
public class Kiwi {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String kiwiUuid;
private double purchasePrice;
private LocalDateTime purchaseTimestamp;
@ManyToOne(fetch=LAZY)
// @ManyToOne
@JoinColumn(name="basketUuid")
private Basket basket;
public Kiwi() {
// this(UUID.randomUUID().toString(), 1.0, LocalDateTime.now(), new Basket());
}
public Kiwi(String kiwiUuid, double purchasePrice, LocalDateTime purchaseTimestamp, Basket basket) {
this.kiwiUuid = kiwiUuid;
this.purchasePrice = purchasePrice;
this.purchaseTimestamp = purchaseTimestamp;
this.basket = basket;
}
public String getKiwiUuid() {
return kiwiUuid;
}
public void setKiwiUuid(String kiwiUuid) {
this.kiwiUuid = kiwiUuid;
}
public double getPurchasePrice() {
return purchasePrice;
}
public void setPurchasePrice(double purchasePrice) {
this.purchasePrice = purchasePrice;
}
public LocalDateTime getPurchaseTimestamp() {
return purchaseTimestamp;
}
public void setPurchaseTimestamp(LocalDateTime purchaseTimestamp) {
this.purchaseTimestamp = purchaseTimestamp;
}
public Basket getBasket() {
return basket;
}
public void setBasket(Basket basket) {
this.basket = basket;
}
}
StringListConverter.java:
package com.mycompany.myapp;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.util.Arrays;
import static java.util.Collections.emptyList;
import java.util.List;
@Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {
private static final String DELIMITER = ";";
@Override
public String convertToDatabaseColumn(List<String> stringList) {
return stringList != null ? String.join(DELIMITER, stringList) : "";
}
@Override
public List<String> convertToEntityAttribute(String string) {
return string != null ? Arrays.asList(string.split(DELIMITER)) : emptyList();
}
}
WatermelonConverter.java:
package com.mycompany.myapp;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.time.LocalDateTime;
@Converter
public class WatermelonConverter implements AttributeConverter<Watermelon,String> {
private static final String SEPARATOR = ";";
@Override
public String convertToDatabaseColumn(Watermelon watermelon) {
StringBuilder sb = new StringBuilder();
sb.append( watermelon.getWatermelonUuid() );
sb.append( SEPARATOR );
sb.append( watermelon.getPurchasePrice());
sb.append( SEPARATOR );
sb.append( watermelon.getPurchaseTimestamp());
return sb.toString();
}
@Override
public Watermelon convertToEntityAttribute(String dbWatermelon) {
String[] parts = dbWatermelon.split(SEPARATOR);
Watermelon watermelon = new Watermelon();
int i = 0;
watermelon.setWatermelonUuid( parts[i++] );
watermelon.setPurchasePrice(Double.parseDouble(parts[i++]) );
watermelon.setPurchaseTimestamp(LocalDateTime.parse(parts[i++]));
return watermelon;
}
}
Watermelon.java:
package com.mycompany.myapp;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import java.time.LocalDateTime;
@Entity
public class Watermelon {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String watermelonUuid;
private double purchasePrice;
@Basic
private LocalDateTime purchaseTimestamp;
@OneToOne
@JoinColumn(name="basketUuid")
private Basket basket;
public Watermelon() {
// this(0.0, LocalDateTime.now());
}
public Watermelon(double purchasePrice, LocalDateTime purchaseTimestamp) {
this.purchasePrice = purchasePrice;
this.purchaseTimestamp = purchaseTimestamp;
}
public String getWatermelonUuid() {
return watermelonUuid;
}
public void setWatermelonUuid(String uuid) {
this.watermelonUuid = uuid;
}
public double getPurchasePrice() {
return purchasePrice;
}
public void setPurchasePrice(double purchasePrice) {
this.purchasePrice = purchasePrice;
}
public LocalDateTime getPurchaseTimestamp() {
return purchaseTimestamp;
}
public void setPurchaseTimestamp(LocalDateTime purchaseTimestamp) {
this.purchaseTimestamp = purchaseTimestamp;
}
}
hibernate.cfg.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.sqlite.JDBC</property>
<property name="connection.url">jdbc:sqlite:mydatabase.sqlite</property>
<!-- SQL dialect for SQLite -->
<!-- <property name="dialect">org.hibernate.dialect.SQLiteDialect</property> -->
<property name="hibernate.dialect">org.hibernate.community.dialect.SQLiteDialect</property>
<property name="hbm2ddl.auto">update</property>
<property name="show_sql">true</property>
<mapping class="com.mycompany.myapp.Basket"/>
<mapping class="com.mycompany.myapp.Watermelon"/>
<mapping class="com.mycompany.myapp.Kiwi"/>
</session-factory>
</hibernate-configuration>
Relevant part of output of code (in NetBeans):
--- exec:3.1.0:exec (default-cli) @ MiniProjectForForumHelp ---
Oct. 02, 2023 1:02:54 A.M. org.hibernate.Version logVersion
INFO: HHH000412: Hibernate ORM core version 6.3.0.Final
Oct. 02, 2023 1:02:55 A.M. org.hibernate.cache.internal.RegionFactoryInitiator initiateService
INFO: HHH000026: Second-level cache disabled
Oct. 02, 2023 1:02:55 A.M. org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using built-in connection pool (not intended for production use)
Oct. 02, 2023 1:02:55 A.M. org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: Loaded JDBC driver class: org.sqlite.JDBC
Oct. 02, 2023 1:02:55 A.M. org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001012: Connecting with JDBC URL [jdbc:sqlite:mydatabase.sqlite]
Oct. 02, 2023 1:02:55 A.M. org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {}
Oct. 02, 2023 1:02:55 A.M. org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Oct. 02, 2023 1:02:55 A.M. org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections <init>
INFO: HHH10001115: Connection pool size: 20 (min=1)
Oct. 02, 2023 1:02:56 A.M. org.hibernate.validator.internal.util.Version <clinit>
INFO: HV000001: Hibernate Validator 8.0.1.Final
Oct. 02, 2023 1:02:58 A.M. org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
Oct. 02, 2023 1:02:58 A.M. org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@2bb62414] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Hibernate: create table Basket (basketUuid varchar(255) not null, brandName varchar(255), primary key (basketUuid))
Hibernate: create table Kiwi (kiwiUuid varchar(255) not null, purchasePrice float not null, purchaseTimestamp timestamp, basketUuid varchar(255), primary key (kiwiUuid))
Hibernate: create table Watermelon (watermelonUuid varchar(255) not null, purchasePrice float not null, purchaseTimestamp timestamp, basketUuid varchar(255) unique, primary key (watermelonUuid))
Hibernate: alter table Watermelon drop constraint UK_ad6t7ccky3nwuethuv9mreq90
Hibernate: alter table Watermelon add constraint UK_ad6t7ccky3nwuethuv9mreq90 unique (basketUuid)
Hibernate: select b1_0.basketUuid,b1_0.brandName,w1_0.watermelonUuid,w1_0.purchasePrice,w1_0.purchaseTimestamp,k1_0.basketUuid,k1_0.kiwiUuid,k1_0.purchasePrice,k1_0.purchaseTimestamp from Basket b1_0 left join Watermelon w1_0 on b1_0.basketUuid=w1_0.basketUuid left join Kiwi k1_0 on b1_0.basketUuid=k1_0.basketUuid where b1_0.basketUuid=?
Hibernate: insert into Basket (brandName,basketUuid) values (?,?)
Hibernate: insert into Kiwi (basketUuid,purchasePrice,purchaseTimestamp,kiwiUuid) values (?,?,?,?)
Hibernate: insert into Kiwi (basketUuid,purchasePrice,purchaseTimestamp,kiwiUuid) values (?,?,?,?)
Hibernate: insert into Watermelon (basketUuid,purchasePrice,purchaseTimestamp,watermelonUuid) values (?,?,?,?)
Here are the tables (which I exported from sqlitebrowser as csv files which I am printing out here).:
user@debian:~/some_directory$ ls -a
. .. Basket.csv Kiwi.csv Watermelon.csv
user@debian:~/some_directory$ cat ./Basket.csv
basketUuid,brandName
597eea79-2ae6-4c75-8010-831cd1b718cf,Tote
user@debian:~/some_directory$ cat ./Kiwi.csv
kiwiUuid,purchasePrice,purchaseTimestamp,basketUuid
502fe99a-374a-4b6e-992c-703ea322ac54,1.0,1696222973854,
a794ccc1-021e-4b2a-89b8-b50053281ede,1.0,1696223033854,
user@debian:~/some_directory$ cat ./Watermelon.csv
watermelonUuid,purchasePrice,purchaseTimestamp,basketUuid
4f8768a9-4ca0-45e5-be0f-63f9bf9c0fe9,10.0,1696222973845,
user@debian:~/some_directory$
Notice that the foreign keys in Kiwi and Watermelon (which is the primary key of Basket) are null (the nothingness that follows the last comma).
So, could someone please help me figure out what I’m not grasping properly?
Any input would be greatly appreciated!