Kotlin Spring JPA/Hibernate – Composite Primary Key + SpringBoot + MySQL

In the tutorial, JavaSampleApproach will introduce step-by-step to create Composite Primary Key with @Embeddable annotation by Kotlin Spring JPA/Hibernate application.

I. Technologies

– Java 1.8
– Kotlin 1.2.20
– Apache Maven 3.5.2
– Spring Tool Suite – Version 3.9.2.RELEASE
– Spring Boot – 1.5.10.RELEASE
– MySQL database

II. Overview

The tutorial will create a sample project with 2 entities: Customer entity (mapping with customer table) and OrderDetail entity (mapping with order_detail table). And they have One-to-Many relationship.
– customer table has a composite primary key: {customer_id, brandcode}.
– order_detail table has a primary key: order_id and a foreign key: {customer_id, brandcode}.

kotlin spring jpa - composite primary key - relationship diagram
To define a Composite Primary Key, we use javax.persistence.Embeddable annotation. @Embeddable is used to indicate a class whose instances are stored as intrinsic part of the owning entity.

– Customer class:

package com.javasampleapproach.springjpa.compositeprimarykey.model

import javax.persistence.CascadeType;
import javax.persistence.EmbeddedId
import javax.persistence.Entity
import javax.persistence.OneToMany

import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
  
@Entity
class Customer(
	@EmbeddedId
	var customerId: CustomerId? = null,
	var company: String = "",
	var name: String = "",
	
	@OneToMany(mappedBy = "customer", cascade = arrayOf(CascadeType.ALL))
    var orderDetails: Set? = null
){
	override fun toString(): String{
		val mapper = ObjectMapper()
		mapper.enable(SerializationFeature.INDENT_OUTPUT)
		val jsonString = mapper.writeValueAsString(this)
		return jsonString 
	}
}

– CustomerId class:

package com.javasampleapproach.springjpa.compositeprimarykey.model

import java.io.Serializable

import javax.persistence.Column
import javax.persistence.Embeddable
 
@Embeddable
class CustomerId(
	@Column(name = "customer_id")
	var customerId: Int = -1,
	
	@Column(name = "brandcode")
	var brandcode: String = ""
): Serializable

– OrderDetail class:

package com.javasampleapproach.springjpa.compositeprimarykey.model

import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.JoinColumn
import javax.persistence.JoinColumns
import javax.persistence.ManyToOne
import javax.persistence.Table

import com.fasterxml.jackson.annotation.JsonIgnore
 
@Entity
@Table(name="order_detail")
class OrderDetail(
	@Id
	@Column(name="order_id")
	var orderId: String = "",
	
	
	@JsonIgnore
    @ManyToOne
    @JoinColumns(JoinColumn(name = "brandcode", referencedColumnName = "brandcode"),
						JoinColumn(name = "customer_id", referencedColumnName = "customer_id"))
    var customer: Customer? = null,
	var product: String = ""
)

III. Practice

Step to do
– Create Kotlin Spring Boot project
– Create entities class
– Create JPA Repositories & Services
– Create a test client
– Configure JPA and datasource
– Run & check results

kotlin spring jpa - composite primary key - project structure

1. Create Kotlin Spring Boot project

– Using SpringToolSuite, create a Kotlin SpringBoot project. Then add needed dependencies:


	org.springframework.boot
	spring-boot-starter-data-jpa


    com.fasterxml.jackson.core
    jackson-databind


	mysql
	mysql-connector-java
	runtime

2. Create entities class

– Create Customer entity:

package com.javasampleapproach.springjpa.compositeprimarykey.model

import javax.persistence.CascadeType;
import javax.persistence.EmbeddedId
import javax.persistence.Entity
import javax.persistence.OneToMany

import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
  
@Entity
class Customer(
	@EmbeddedId
	var customerId: CustomerId? = null,
	var company: String = "",
	var name: String = "",
	
	@OneToMany(mappedBy = "customer", cascade = arrayOf(CascadeType.ALL))
    var orderDetails: Set? = null
){
	override fun toString(): String{
		val mapper = ObjectMapper()
		mapper.enable(SerializationFeature.INDENT_OUTPUT)
		val jsonString = mapper.writeValueAsString(this)
		return jsonString 
	}
}

– Create CustomerId class:

package com.javasampleapproach.springjpa.compositeprimarykey.model

import java.io.Serializable

import javax.persistence.Column
import javax.persistence.Embeddable
 
@Embeddable
class CustomerId(
	@Column(name = "customer_id")
	var customerId: Int = -1,
	
	@Column(name = "brandcode")
	var brandcode: String = ""
): Serializable

– Create OrderDetail entity:

package com.javasampleapproach.springjpa.compositeprimarykey.model

import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.JoinColumn
import javax.persistence.JoinColumns
import javax.persistence.ManyToOne
import javax.persistence.Table

import com.fasterxml.jackson.annotation.JsonIgnore
 
@Entity
@Table(name="order_detail")
class OrderDetail(
	@Id
	@Column(name="order_id")
	var orderId: String = "",
	
	
	@JsonIgnore
    @ManyToOne
    @JoinColumns(JoinColumn(name = "brandcode", referencedColumnName = "brandcode"),
						JoinColumn(name = "customer_id", referencedColumnName = "customer_id"))
    var customer: Customer? = null,
	var product: String = ""
)
3. Create JPA Repositories & Services
3.1 Create JPA repositories

– CustomerRepository:

package com.javasampleapproach.springjpa.compositeprimarykey.repository

import org.springframework.data.jpa.repository.JpaRepository
 
import com.javasampleapproach.springjpa.compositeprimarykey.model.Customer
import com.javasampleapproach.springjpa.compositeprimarykey.model.CustomerId

interface CustomerRepository: JpaRepository

– OrderRepository:

package com.javasampleapproach.springjpa.compositeprimarykey.repository

import org.springframework.data.jpa.repository.JpaRepository
 
import com.javasampleapproach.springjpa.compositeprimarykey.model.OrderDetail

interface OrderRepository: JpaRepository
3.2 Create Services

– CustomerServices:

package com.javasampleapproach.springjpa.compositeprimarykey.service

import javax.transaction.Transactional
 
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
 
import com.javasampleapproach.springjpa.compositeprimarykey.model.Customer
import com.javasampleapproach.springjpa.compositeprimarykey.repository.CustomerRepository
 
@Service
public class CustomerServices {
	
	@Autowired
	lateinit var customerRepository: CustomerRepository
	
	fun deleteAll(){
		customerRepository.deleteAll()
	}
	
	fun save(customer: Customer){
		customerRepository.save(customer)
		customerRepository.flush()
	}
	
	@Transactional
	fun showAll(){
		val custs = customerRepository.findAll()
		custs.forEach{println(it)}
	}
}

– OrderServices:

package com.javasampleapproach.springjpa.compositeprimarykey.service

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
 
import com.javasampleapproach.springjpa.compositeprimarykey.model.OrderDetail
import com.javasampleapproach.springjpa.compositeprimarykey.repository.OrderRepository
 
@Service
class OrderServices {
	
	@Autowired
	lateinit var orderRepository: OrderRepository
	
	fun save(order: OrderDetail){
		orderRepository.save(order)
	}
	
	fun deleteAll(){
		orderRepository.deleteAll()
	}
}
4. Create a test client
package com.javasampleapproach.springjpa.compositeprimarykey

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication

import org.springframework.beans.factory.annotation.Autowired

import org.springframework.boot.CommandLineRunner

import com.javasampleapproach.springjpa.compositeprimarykey.model.*
import com.javasampleapproach.springjpa.compositeprimarykey.service.CustomerServices
import com.javasampleapproach.springjpa.compositeprimarykey.service.OrderServices

import org.springframework.context.annotation.Bean

@SpringBootApplication
class KotlinSpringJpaCompositePrimaryKeyApplication{
	@Autowired
	lateinit var customerService: CustomerServices
	
	@Autowired
	lateinit var orderServices: OrderServices
	
	@Bean
	fun run() = CommandLineRunner {
		deleteAll()
		saveData()
		showAll()
	}
	
	fun deleteAll(){
		println("---> Delete Customers")
		orderServices.deleteAll()
		customerService.deleteAll()
	}
	
	fun saveData(){
		println("---> Store Customers")
		// ===============Create customers===============
		// 1. Jack
		val jackId = CustomerId(1000, "azc")
		val jack = Customer(jackId, "A & Z", "Jack")
		
		val jackIphoneOrder = OrderDetail("001", jack, "IPhone 7")
		val jackIPadMiniOrder = OrderDetail("002", jack, "IPad Mini 2")
		
		val jackOrderDetails = setOf(jackIphoneOrder, jackIPadMiniOrder)
		jack.orderDetails = jackOrderDetails
		
		// 2. Mary
		val maryId = CustomerId(2000, "mkl")
		val mary = Customer(maryId, "Fashion Company", "Mary")
		
		val maryNote7Order = OrderDetail("003", mary, "Samsung Galaxy Note 7")
		
		val maryOrderDetails = setOf(maryNote7Order)
		mary.orderDetails = maryOrderDetails
		
		// ===============Saving to DB===============
		customerService.save(jack)
		customerService.save(mary)
	}
	
	fun showAll(){
		println("---> Show Customers' Info")
		customerService.showAll()
	}
}

fun main(args: Array) {
    SpringApplication.run(KotlinSpringJpaCompositePrimaryKeyApplication::class.java, *args)
}

5. Configure JPA and datasource

– Open application.properties file, configure spring.datasource & spring.jpa:

spring.datasource.url=jdbc:mysql://localhost:3306/testdb
spring.datasource.username=root
spring.datasource.password=12345
 
spring.jpa.generate-ddl=true
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

– If set spring.jpa.generate-ddl is true, 2 tables: customer & order_detail will be created automatically by Hibernate.
Otherwise, if set spring.jpa.generate-ddl is false, we can use below SQL scripts to create the tables:

drop table if exists testdb.customer;
create table customer(
	customer_id INT NOT NULL AUTO_INCREMENT,
	brandcode VARCHAR(25) NOT NULL,
	company VARCHAR(25) NOT NULL,
	name VARCHAR(25) NOT NULL,
	PRIMARY KEY (customer_id, brandcode)
)
 
drop talbe if exists testdb.order_detail;
create table order_detail(
	order_id VARCHAR(25) NOT NULL AUTO_INCREMENT,
	customer_id INT NOT NULL,
	brandcode VARCHAR(25) NOT NULL,
	product VARCHAR(100) NOT NULL,
	PRIMARY KEY (order_id),
	CONSTRAINT FK_order_detail FOREIGN KEY (customer_id,brandcode)
    REFERENCES customer(customer_id,brandcode)
)
6. Run & check results

Run the project with SpringBoot App mode.
-> Logs:

---> Delete Customers
---> Store Customers
---> Show Customers' Info
{
  "customerId" : {
    "customerId" : 1000,
    "brandcode" : "azc"
  },
  "company" : "A & Z",
  "name" : "Jack",
  "orderDetails" : [ {
    "orderId" : "002",
    "product" : "IPad Mini 2"
  }, {
    "orderId" : "001",
    "product" : "IPhone 7"
  } ]
}
{
  "customerId" : {
    "customerId" : 2000,
    "brandcode" : "mkl"
  },
  "company" : "Fashion Company",
  "name" : "Mary",
  "orderDetails" : [ {
    "orderId" : "003",
    "product" : "Samsung Galaxy Note 7"
  } ]
}

Database tables:

-> customer table:

kotlin spring jpa - composite primary key - customer table

-> order_detail table:

kotlin spring jpa - composite primary key - order details table

IV. SourceCode

KotlinSpringJPACompositePrimaryKey

One thought on “Kotlin Spring JPA/Hibernate – Composite Primary Key + SpringBoot + MySQL”

  1. Today, I went to the beachfront with my kids.
    I found a sea shell and gave it to my 4 year old
    daughter and said “You can hear the ocean if you put this to your ear.” She
    put the shell to her ear and screamed. There was a hermit crab inside and it pinched her ear.
    She never wants to go back! LoL I know this is completely off topic but I had to tell someone!

Leave a Reply

Your email address will not be published. Required fields are marked *