Thursday, April 12, 2018

Hibernate one to many relationship persistence

Introduction

The example below is based on the application https://www.mkyong.com/hibernate/hibernate-one-to-many-relationship-example-annotation. I modified it a little bit to show how to automatically save the "many" records when the "one" record is saved in a one-to-many relationship.

pom.xml

<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/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.mkyong.common</groupId>
 <artifactId>HibernateExample</artifactId>
 <packaging>jar</packaging>
 <version>1.0</version>
 <name>HibernateExample</name>
 <url>http://maven.apache.org</url>

 <repositories>
  <repository>
   <id>JBoss repository</id>
   <url>http://repository.jboss.org/nexus/content/groups/public/</url>
  </repository>
 </repositories>
 
  <build>
        <pluginManagement>
          <plugins>
            <plugin>
              <artifactId>maven-resources-plugin</artifactId>
              <version>2.7</version>
            </plugin>           
          </plugins>
        </pluginManagement>   
    </build>

 <dependencies>

  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.8.2</version>
   <scope>test</scope>
  </dependency>

  <!-- MySQL database driver -->
  <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.15</version>
  </dependency>

  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-core</artifactId>
   <version>3.6.3.Final</version>
  </dependency>

  <dependency>
   <groupId>javassist</groupId>
   <artifactId>javassist</artifactId>
   <version>3.12.1.GA</version>
  </dependency>

  <!-- logback logging framework-->
  <dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-core</artifactId>
   <version>0.9.28</version>
  </dependency>

  <dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-classic</artifactId>
   <version>0.9.28</version>
  </dependency>


 </dependencies>
</project>

Database scripts


DROP TABLE IF EXISTS `stock`;
CREATE TABLE `stock` (
  `STOCK_ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `STOCK_CODE` varchar(10) NOT NULL,
  `STOCK_NAME` varchar(20) NOT NULL,
  PRIMARY KEY (`STOCK_ID`) USING BTREE,
  UNIQUE KEY `UNI_STOCK_NAME` (`STOCK_NAME`),
  UNIQUE KEY `UNI_STOCK_ID` (`STOCK_CODE`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `mkyongdb`.`stock_daily_record`;
CREATE TABLE  `mkyongdb`.`stock_daily_record` (
  `RECORD_ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `PRICE_OPEN` float(6,2) DEFAULT NULL,
  `PRICE_CLOSE` float(6,2) DEFAULT NULL,
  `PRICE_CHANGE` float(6,2) DEFAULT NULL,
  `VOLUME` bigint(20) unsigned DEFAULT NULL,
  `DATE` date NOT NULL,
  `STOCK_ID` int(10) unsigned NOT NULL,
  PRIMARY KEY (`RECORD_ID`) USING BTREE,
  UNIQUE KEY `UNI_STOCK_DAILY_DATE` (`DATE`),
  KEY `FK_STOCK_TRANSACTION_STOCK_ID` (`STOCK_ID`),
  CONSTRAINT `FK_STOCK_TRANSACTION_STOCK_ID` FOREIGN KEY (`STOCK_ID`) 
  REFERENCES `stock` (`STOCK_ID`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8;

Domain classes

package com.mkyong.stock;

import static javax.persistence.GenerationType.IDENTITY;

import java.util.LinkedList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@Table(name = "stock", catalog = "workshop", uniqueConstraints = { @UniqueConstraint(columnNames = "STOCK_NAME"),
  @UniqueConstraint(columnNames = "STOCK_CODE") })
public class Stock implements java.io.Serializable {

 private Integer stockId;
 private String stockCode;
 private String stockName;
 private List stockDailyRecords = new LinkedList();

 public Stock() {
 }

 public Stock(String stockCode, String stockName) {
  this.stockCode = stockCode;
  this.stockName = stockName;
 }

 public Stock(String stockCode, String stockName, List stockDailyRecords) {
  this.stockCode = stockCode;
  this.stockName = stockName;
  this.stockDailyRecords = stockDailyRecords;
 }

 @Id
 @GeneratedValue(strategy = IDENTITY)
 @Column(name = "STOCK_ID", unique = true, nullable = false)
 public Integer getStockId() {
  return this.stockId;
 }

 public void setStockId(Integer stockId) {
  this.stockId = stockId;
 }

 @Column(name = "STOCK_CODE", unique = true, nullable = false, length = 10)
 public String getStockCode() {
  return this.stockCode;
 }

 public void setStockCode(String stockCode) {
  this.stockCode = stockCode;
 }

 @Column(name = "STOCK_NAME", unique = true, nullable = false, length = 20)
 public String getStockName() {
  return this.stockName;
 }

 public void setStockName(String stockName) {
  this.stockName = stockName;
 }

 @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "stock")
 public List getStockDailyRecords() {
  return this.stockDailyRecords;
 }

 public void setStockDailyRecords(List stockDailyRecords) {
  this.stockDailyRecords = stockDailyRecords;
 }

}

package com.mkyong.stock;

import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;

@Entity
@Table(name = "stock_daily_record", catalog = "workshop", uniqueConstraints = @UniqueConstraint(columnNames = "DATE"))
public class StockDailyRecord implements java.io.Serializable {

 private Integer recordId;
 private Stock stock;
 private Float priceOpen;
 private Float priceClose;
 private Float priceChange;
 private Long volume;
 private Date date;

 public StockDailyRecord() {
 }

 public StockDailyRecord(Stock stock, Date date) {
  this.stock = stock;
  this.date = date;
 }

 public StockDailyRecord(Stock stock, Float priceOpen, Float priceClose,
   Float priceChange, Long volume, Date date) {
  this.stock = stock;
  this.priceOpen = priceOpen;
  this.priceClose = priceClose;
  this.priceChange = priceChange;
  this.volume = volume;
  this.date = date;
 }

 @Id
 @GeneratedValue(strategy = IDENTITY)
 @Column(name = "RECORD_ID", unique = true, nullable = false)
 public Integer getRecordId() {
  return this.recordId;
 }

 public void setRecordId(Integer recordId) {
  this.recordId = recordId;
 }

 @ManyToOne(fetch = FetchType.LAZY)
 @JoinColumn(name = "STOCK_ID", nullable = false)
 public Stock getStock() {
  return this.stock;
 }

 public void setStock(Stock stock) {
  this.stock = stock;
 }

 @Column(name = "PRICE_OPEN", precision = 6)
 public Float getPriceOpen() {
  return this.priceOpen;
 }

 public void setPriceOpen(Float priceOpen) {
  this.priceOpen = priceOpen;
 }

 @Column(name = "PRICE_CLOSE", precision = 6)
 public Float getPriceClose() {
  return this.priceClose;
 }

 public void setPriceClose(Float priceClose) {
  this.priceClose = priceClose;
 }

 @Column(name = "PRICE_CHANGE", precision = 6)
 public Float getPriceChange() {
  return this.priceChange;
 }

 public void setPriceChange(Float priceChange) {
  this.priceChange = priceChange;
 }

 @Column(name = "VOLUME")
 public Long getVolume() {
  return this.volume;
 }

 public void setVolume(Long volume) {
  this.volume = volume;
 }

 @Temporal(TemporalType.DATE)
 @Column(name = "DATE", unique = true, nullable = false, length = 10)
 public Date getDate() {
  return this.date;
 }

 public void setDate(Date date) {
  this.date = date;
 }

}


Test program

package com.mkyong;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import org.hibernate.Session;

import com.mkyong.stock.Stock;
import com.mkyong.stock.StockDailyRecord;
import com.mkyong.util.HibernateUtil;

/**
 * Note that this project is from
 * https://www.mkyong.com/hibernate/hibernate-one-to-many-relationship-example-annotation/
 * 
 * @author yxu
 *
 */
public class AppList {
 public static void main(String[] args) {
  System.out.println("Hibernate one to many (Annotation)");
  Session session = HibernateUtil.getSessionFactory().openSession();

  session.beginTransaction();

  Stock stock = new Stock();
  stock.setStockCode("70523");
  stock.setStockName("PADIN2");
  // session.save(stock);

  List stockDailyRecordsSet = new LinkedList();

  for (int i = 0; i < 3; i++) {
   StockDailyRecord stockDailyRecords = new StockDailyRecord();
   stockDailyRecords.setPriceOpen(new Float("1.2"));
   stockDailyRecords.setPriceClose(new Float("1.1"));
   stockDailyRecords.setPriceChange(new Float("10.0"));
   stockDailyRecords.setVolume(3000000L);
   stockDailyRecords.setDate(new Date(2018, 5, 16 + i));

   stockDailyRecords.setStock(stock);
   stockDailyRecordsSet.add(stockDailyRecords);
  }

  stock.setStockDailyRecords(stockDailyRecordsSet);

  session.save(stock);

  session.getTransaction().commit();
  System.out.println("Done");
 }
}

Run AppList, you should see that it creates the stock and the 3 StockDailyRecord records in the database.

Note that I used List as the collection type in Stock for StockDailyRecord. You can also use Set.

Note that the annotation "@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "stock")" in the class Stock is the most important in the setup. Without "casecade=CascadeType.ALL", the StockDailyRecord will not be saved into the database when the Stock is saved.

Both the tables STOCK and STOCK_DAILY_RECORD use the AUTO_INCREMENT ID as the primary key. And the Java annotation uses @GeneratedValue(strategy = IDENTITY). If a sequence is used to generate the ID, change the annotation to something like below:

 @Id
 @Column(name = "MY_ID")
 @SequenceGenerator(name = "MY_SEQ", sequenceName = "MY_SEQ", allocationSize=1)
 @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="MY_SEQ")
 public Integer getMyId() {
  return this.myId;
 }

Other classes and files

package com.mkyong.util;
 
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
 
public class HibernateUtil {
 
 private static final SessionFactory sessionFactory = buildSessionFactory();
 
 private static SessionFactory buildSessionFactory() {
  try {
   // Create the SessionFactory from hibernate.cfg.xml
   return new Configuration().configure().buildSessionFactory();
  } catch (Throwable ex) {
   // Make sure you log the exception, as it might be swallowed
   System.err.println("Initial SessionFactory creation failed." + ex);
   throw new ExceptionInInitializerError(ex);
  }
 }
 
 public static SessionFactory getSessionFactory() {
  return sessionFactory;
 }
 
 public static void shutdown() {
  // Close caches and connection pools
  getSessionFactory().close();
 }
 
}
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>
   >property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver>/property>
  >property name="hibernate.connection.url">jdbc:mysql://localhost:3306/workshop>/property>
  >property name="hibernate.connection.username">workshop>/property>
  >property name="hibernate.connection.password">za>/property>
  >property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect>/property>
  >property name="show_sql">true>/property>
  >property name="format_sql">true>/property>
  >mapping class="com.mkyong.stock.Stock" />
  >mapping class="com.mkyong.stock.StockDailyRecord" />
 >/session-factory>
>/hibernate-configuration>