Wednesday, 28 April 2021

Spring jpa: query method to find by embedded id object property

A composite key is a combination of two or more columns in a table that can be used to uniquely identify each row in the table

 

For example, take ‘projects’ table.

Project_Name

Organization

Started_Time

Active

Description

 

 

 

 

 

 

 

 

 

 

 

In the above table two columns ‘PROJECT_NAME’ and ‘ORGANIZATION’ together form a composite key.

 

How to design composite key scenario?

Step 1: Create a class that has properties projectName and organization. Annotate this class with @Embeddable annotation.

@Embeddable
public class ProjectId implements Serializable {

  private static final long serialVersionUID = 1L;

  @Column(name = "PROJECT_NAME")
  private String projectName;

  @Column(name = "ORGANIZATION")
  private String organization;

  ......
  ......
}

 

Step 2: Create Project class that contain ProjectId as embedded id.

@Entity
@Table(name = "projects")
public class Project {

  @EmbeddedId
  private ProjectId projectId;

  @Column(name = "STARTED_TIME")
  private Timestamp startedTime;

  @Column(name = "ACTIVE")
  private String active;

  @Column(name = "DESCRIPTION")
  private String description;

  ......
  ......
}

 

How to query on embeddedId property using query method?

List<Project> findByProjectIdProjectName(String projectName);

 

Here Project has an ProjectId with a projectName. In that case, the method creates the x.projectId.projectName property traversal. The resolution algorithm starts by interpreting the entire part (ProjectIdProjectName) as the property and checks the domain class for a property with that name (uncapitalized). If the algorithm succeeds, it uses that property. If not, the algorithm splits up the source at the camel-case parts from the right side into a head and a tail and tries to find the corresponding propertyin our example, ProjectIdProject and Code. If the algorithm finds a property with that head, it takes the tail and continues building the tree down from there, splitting the tail up in the way just described. If the first split does not match, the algorithm moves the split point to the left (ProjectId, ProjectName) and continues.

 

Problem with above approach

Although this should work for most cases, it is possible for the algorithm to select the wrong property. Suppose the Project class has an ProjectIdProject property as well. The algorithm would match in the first split round already, choose the wrong property, and fail.

 

How to resolve the ambiguity?

Use _ inside your method name to manually define traversal points.

 

Example

List<Project> findByProjectId_ProjectName(String projectName); 

List<Project> findByProjectId_Organization(String organization);

List<Project> findByProjectId_ProjectNameAndProjectId_Organization(String projectName, String organization);

 

Find the below working application.

 

Step 1: Create new maven project ‘query-method-on-embeddedId-demo’.

 

Step 2: Update pom.xml with maven dependencies.

 

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.sample.app</groupId>
  <artifactId>query-method-on-embeddedId-demo</artifactId>
  <version>1</version>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.0</version>
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>


  <dependencies>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>


    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
    </dependency>


  </dependencies>
</project>

 

Step 3: Create application.properties file under src/main/resources folder.

 

application.properties

logging.level.root=WARN
logging.level.org.hibernate=ERROR

## H2 specific properties
spring.h2.console.enabled=true
spring.h2.console.path=/h2

spring.datasource.url=jdbc:h2:file:~/db/myOrg.db;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1;

spring.datasource.username=krishna
spring.datasource.password=password123

spring.datasource.driverClassName=org.h2.Driver

## JPA specific properties
# Creates the schema, destroying previous data.
spring.jpa.hibernate.ddl-auto=create

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=false

## Database connection pooling properties
# Number of ms to wait before throwing an exception if no connection is available.
spring.datasource.max-wait=10000

# Maximum number of active connections that can be allocated from this pool at the same time.
spring.datasource.tomcat.max-active=10
spring.datasource.tomcat.max-idle=5
spring.datasource.tomcat.min-idle=3

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

 

Step 4: Define Project and ProjectId entities.

 

ProjectId.java

package com.sample.app.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Embeddable;

@Embeddable
public class ProjectId implements Serializable {

  private static final long serialVersionUID = 1L;

  @Column(name = "PROJECT_NAME")
  private String projectName;

  @Column(name = "ORGANIZATION")
  private String organization;

  public ProjectId() {

  }

  public ProjectId(String projectName, String organization) {
    this.projectName = projectName;
    this.organization = organization;
  }

  public String getProjectName() {
    return projectName;
  }

  public void setProjectName(String projectName) {
    this.projectName = projectName;
  }

  public String getOrganization() {
    return organization;
  }

  public void setOrganization(String organization) {
    this.organization = organization;
  }

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("ProjectId [projectName=");
    builder.append(projectName);
    builder.append(", organization=");
    builder.append(organization);
    builder.append("]");
    return builder.toString();
  }

}

 

Project.java

package com.sample.app.entity;

import java.sql.Timestamp;

import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Table;

@Entity
@Table(name = "projects")
public class Project {

  @EmbeddedId
  private ProjectId projectId;

  @Column(name = "STARTED_TIME")
  private Timestamp startedTime;

  @Column(name = "ACTIVE")
  private String active;

  @Column(name = "DESCRIPTION")
  private String description;

  public Project() {

  }

  public Project(String projectName, String organization, Timestamp startedTime, String active, String description) {
    ProjectId projectId = new ProjectId(projectName, organization);
    this.projectId = projectId;
    this.startedTime = startedTime;
    this.active = active;
    this.description = description;
  }

  public ProjectId getProjectId() {
    return projectId;
  }

  public void setProjectId(ProjectId projectId) {
    this.projectId = projectId;
  }

  public Timestamp getStartedTime() {
    return startedTime;
  }

  public void setStartedTime(Timestamp startedTime) {
    this.startedTime = startedTime;
  }

  public String getActive() {
    return active;
  }

  public void setActive(String active) {
    this.active = active;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("Project [projectId=");
    builder.append(projectId.toString());
    builder.append(", startedTime=");
    builder.append(startedTime);
    builder.append(", active=");
    builder.append(active);
    builder.append(", description=");
    builder.append(description);
    builder.append("]");
    return builder.toString();
  }

}

 

Step 5: Define ProjectRepository.

 

ProjectRepository.java

 

 

package com.sample.app.repository;

import java.util.List;

import org.springframework.data.repository.PagingAndSortingRepository;

import com.sample.app.entity.Project;
import com.sample.app.entity.ProjectId;

public interface ProjectRepository extends PagingAndSortingRepository<Project, ProjectId> {

  List<Project> findByProjectId_ProjectName(String projectName);
  
  List<Project> findByProjectId_Organization(String organization);
  
  List<Project> findByProjectId_ProjectNameAndProjectId_Organization(String projectName, String organization);
}

 

Step 6: Define main application class.

 

App.java

package com.sample.app;

import java.sql.Timestamp;
import java.util.Arrays;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.sample.app.entity.Project;
import com.sample.app.repository.ProjectRepository;

@SpringBootApplication
public class App {

  public void printProjects(Iterable<Project> pjts, String msg) {
    System.out.println(msg);
    for (Project pjt : pjts) {
      System.out.println(pjt.getProjectId().getProjectName() + ", " + pjt.getProjectId().getOrganization());
    }

    System.out.println();
  }

  public static void main(String args[]) {
    SpringApplication.run(App.class, args);
  }

  @Bean
  public CommandLineRunner demo(ProjectRepository projectRepository) {
    return (args) -> {
      Timestamp timeStamp = new Timestamp(System.currentTimeMillis());

      Project pjt1 = new Project("Chat Server", "Aero", timeStamp, "y", "To chat with people");
      Project pjt2 = new Project("Cabin Control Engine", "IT Services", timeStamp, "y",
          "To main cabin temperature and pressure");
      Project pjt3 = new Project("Chat Server", "Research Labs", timeStamp, "y", "Search based on user profile");
      Project pjt4 = new Project("CRM", "IT Services", timeStamp, "y", "Customer Relationship management");

      projectRepository.saveAll(Arrays.asList(pjt1, pjt2, pjt3, pjt4));

      Iterable<Project> projects = projectRepository.findAll();
      printProjects(projects, "All Projects");

      projects = projectRepository.findByProjectId_Organization("IT Services");
      printProjects(projects, "All Projects in the organization IT Services");

      projects = projectRepository.findByProjectId_ProjectName("Chat Server");
      printProjects(projects, "All Projects with name Chat Server");
      
      projects = projectRepository.findByProjectId_ProjectNameAndProjectId_Organization("Chat Server", "Aero");
      printProjects(projects, "All Projects with name Chat Server and organization Aero");
      
      

    };
  }
}

 

Total project structure looks like below.

 



Run App.java, you will see below messages in console.

 

All Projects
Chat Server, Aero
Cabin Control Engine, IT Services
Chat Server, Research Labs
CRM, IT Services

All Projects in the organization IT Services
CRM, IT Services
Cabin Control Engine, IT Services

All Projects with name Chat Server
Chat Server, Aero
Chat Server, Research Labs

All Projects with name Chat Server and organization Aero
Chat Server, Aero


You can download complete working application from below link.

https://github.com/harikrishna553/springboot/tree/master/jpa/query-method-on-embeddedId-demo

 

 

 




 

 

 

 

Previous                                                    Next                                                    Home

No comments:

Post a Comment