Introduction to JPQL (JPA)

Introduction to JPQL (JPA)

SQL to retrieve data from a DB (or more precisely, an entity) in JPA is called JPQL.

In SQL, what was written in the from clause is the table name, but in JPQL, the entity is written in the from clause. (This is important.)

JPQL is written with @Query annotation, but the syntax is a little different from the usual SQL syntax.

The following is a simple JPQL example.

@Query("select m from EmpMaster where m.empId = :id")
@Query(value = "select m from EmpMaster wher m.empId = :id")

In the above, m is the alias; select m instead of select *. value = is optional.

In JPQL, it is important to note that the table name should be the name of the entity class, and the column name should be the field name of the entity class.

Even if the actual table name is emp_master, if the entity class corresponding to the table is the EmpMaster class, the JPQL statement should be written as EmpMaster.

Write UPDATE and DELETE statements

To write UPDATE and DELETE statements in JPQL statements, there are some more annotations.

annotation meaning
@Transactional Rollback if class or method terminates abnormally
@Modifying attached to update, insert, and delete statements

A coding example is shown below.

@Modifying
@Query("update ~")

Do a LIKE search

JPQL allows LIKE search. When you add % to a bind variable, for example, use the following description.

@Query(value = "select m EmpMaster from m.empId like :id%")
List<EmpMaster> findBySample(@Param("id") Long aaa)

JPQL has many limitations.

JPQL has many restrictions, so the only way to do this is to write SQL statements.

When writing SQL, set nativeQuery=true. Here is an example

@Query(nativeQuery=true, value="select ~")

Incidentally, interval, DAY, MONTH, YEAR, etc. used in MySQL date calculations cannot be used in JPQL, so you need to set nativeQuery=true.

You can use the FUNCTION expression described below to avoid this problem.

Use the in clause

If the condition is only an in clause, the findByXXIn method can be used, but if nativeQuery=true and an in clause is present, a List is passed as a parameter.

@Repository
public interface HogeRepository extends JpaRepository<HogeEntity, Integer> {
@Query(value = "select * from hoge_tbl " 
             + " where id in :id ", nativeQuery = true)
List<HogeEntity> findById(@Param("id") List<Integer> id);
}

entity

Create an entity class, but there are some rules.

  • Grant @Entity annotation
  • Assign @Id to any of the fields
  • Implement a constructor with no arguments

The @Table annotation is not mandatory, and the @Table annotation is used to set the table name when table name ≠ entity name.

@Column is also not required, and @Column is granted when column name ≠ field name.

Primary Key

Add the @Id annotation to the primary key of the entity.

If the id field is the primary key, the entity class specifies @Id as follows

@Table(name="auto_increment")
@Entity
@Getter
@Setter
public class AutoIncrementtEntity {
  @Id
  @GeneratedValue
  private Integer id;

  @Column(name="name")
  private String name;
}

If the primary key is a surrogate key, add the @GeneratedValue annotation.

However, MySQL raises the error java.sql.SQLSyntaxErrorException: Table ‘schema.hibernate_sequence’ doesn’t exist.

これを回避するには@GeneratedValueアノテーションのstrategy属性にGenerationType.IDENTITYを指定します. (This is a case of MySQL’s AUTO_INCREMENT.)

If @GeneratedValue’s strategy attribute is unspecified, it will be GenerationType.AUTO.

strategy value meaning
GenerationType.AUTO Default automatic generation method (not for MySQL)
GenerationType.IDENTITY MYSQL
GenerationType.SEQUENCE ORACLE or PostgreSQL
TABLE ORACLE
@Table(name="auto_increment")
@Entity
@Getter
@Setter
public class AutoIncrementtEntity { 
  @Id
  @GeneratedValue(GenerationType.IDENTITY)
  private Integer id;

  @Column(name="name")
  private String name;
}

If you are using PostgreSQL and using a Sequence object, you must specify the Sequence object name in the @SequenceGenerator sequenceName attribute.

  /** ID. */
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "id_seq")
  @SequenceGenerator(name = "id_seq",sequenceName = "id_seq",allocationSize = 1)
  @Column(name = "id")
  private Integer id;

Error occurs if the Select clause is not associated with an entity

If JPQL is easy to use, it is fine, but when using a group that requires aggregation by year and month, for example, it can be complicated in many ways.

Entity Classes

@Table
@Entity
@Getter
@Setter
public class Sample {
    @Id
    @Column("date")
    private LocalDate date;
}

If there is such an entity, JPQL will generate an error if the year and month are entered in the Select clause instead of the year and month.

@Query(value = "select data_format(date, '%Y-%m') from Sample")

This is an error because the value is formatted as a year and month for the LocalDate.

The workaround is to create a new class and insert it as a string in the constructor when creating the instance.

Note that nativeQuery cannot be used when using select new (constructor expression).

@Query(value = "select NEW jp.co.confrage.xxx.domain.entity.SampleEntity"
    + " ( "
    + " date AS DT"
    + " ) "
    + " from Sample ")

SampleEntity is just a class like this.

@AllArgsConstructor
@Data
public class SampleEntity {
    private String DT;
}

This is OK. If you want to include @Param arguments (named parameters) in the constructor expression, you need to CAST them.

Note that even if @Param is a String type, you need to CAST it to a String type with CAST. (Confirmed by MySQL)

@Query(value = "SELECT NEW jp.co.confrage.xxx.domain.entity.SampleEntity"
    + " ( "
    + " CAST(:name as java.lang.String)" // こんな感じ
    + " ) "
    + " from Sample ")
List<SampleEntity> findByXXXX(@Param("name") String name); // この引数をコンストラクタ式に入れる場合

The name is a String type, but must be CAST when used as an argument to a constructor expression.

It must be fully qualified.

When constructor expressions are not used

It is not always necessary to use constructor expressions. For example, the following methods can be written

@Query(value="select m.aaa.accountNumber,m.branchNumber from AccountEntity m where m.aaa.id = 1")
List<Object[]> findXXX();

Just make it a List<Object[]>.

We can create the following method in a class such as DTO.

import java.math.BigDecimal;
import java.time.LocalDateTime;

import lombok.AllArgsConstructor;
import lombok.Data;

@AllArgsConstructor
@Data
public class HogeResource {
  private Integer id;
  private BigDecimal version;
  private LocalDateTime date;
  /**
   * Let's create a method like this. Generate resource class.
   */
  public static HogeResource toResource(Object[] obj) {
    return new HogeResource(
      (Integer)obj[0],
      (BigDecimal)obj[1],
      (LocalDateTime)obj[2]);
  }
}

The method caller side of the repository can be neatly coded using stream as follows

List<SampleDto> obj = accountRepository.findXXX()
    .stream()
    .map(SampleDto::toResource)
    .collect(Collectors.toList());

This allows you to write without using a constructor expression.

@EmbeddedId

If there is only one primary key, @Id is sufficient, but for compound primary keys, @IdClass or @EmbeddedId should be used.

There are cases where @IdClass is used, but it tends to be avoided because it is redundant.

Entities are defined as follows

package jp.co.confrage;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Entity
@Table(name="employee_master")
public class Employee {
  @EmbeddedId
  private PrimaryK id; // Class of composite primary key

  @Column(name="empname")
  private String empname;

  @Embeddable
  @Data
  @AllArgsConstructor
  @NoArgsConstructor
  public static class PrimaryK implements Serializable { // Define PrimaryK class as static
    private static final long serialVersionUID = -2523459362991270288L;

    @Column(name="id")
    private String id; // pk

    @Column(name="empno")
    private String empno; // pk
  }
}

If it does not implements Serializable, an error will occur. Therefore, the @Embeddable annotation is added to this compound primary key. The repository class is defined as follows

package jp.co.confrage;

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import jp.co.confrage.Employee.PrimaryK;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, PrimaryK> { // ここ注意
  @Query(value="select m.id.empno from Employee m ")
  public List<Integer> findByPk();
}

For composite primary keys, you have to be careful how you write @Query, and if you use @Embedded, the JPQL will be like m.id.empno.

Note in compound primary key of @Embeddable annotation

It is convenient, but an error occurred when this key was NEW as a constructor expression, so use @IdClass if you want to NEW it as a constructor expression.

Specify the composite primary key as the argument of findById

Let’s specify a composite primary key as an argument to the findById method that JpaRepository provides by default. We will use the entity we wrote earlier.

DI the repository from the service class and call it.

EmployeeRepository repository;
empRepository.findById(new PrimaryK("1","1")); // New compound primary key like this

Generate JPQL from method names

There are certain rules for method names, and JPQL can be generated from those names. Suppose we have an entity named Employee, and the repository is as follows

public Employee findByName(String name);

If you create a method called findByName, it will look like this

from Employee where name = :name

If there are two conditions, findByNameAndEmpno.

public Employee findByNameAndEmpno(String name,String empno);

This method implies the following

from Employee where name = :name and empno = :empno;

The following have special meaning when specified in the method name

単語 メソッド名 JPQL
By findByXX where XX = :XX
And findByXXAndYY where XX = :XX and YY = :YY
Or findByOr where XX = :XX or YY = :YY
Like findByXXLike where XX like :XX
NotLike findByXXNotLike where XX not like :XX
Containing findByXXContaining where XX like ‘%:XX%’
IsNull findByXXIsNull where XX is null
IsNotNull findByXXIsNotNull where XX is not null
NotNull findByXX where XX is not null
Between findByXXBetween where XX between :XX1 and :XX2
LessThan findByXXLessThan where XX < :XX(数値)
GreaterThan findByXXGreaterThan where XX > :XX(数値)
After findByXXAfter where XX > :XX(日時)
Before findByXXBefore where XX < :XX(日時)
OrderBy findByXXOrderByYYAsc where XX = :XX order by YY asc
Not findByXXNot where XX <> :XX
In findByXXIn where XX in (?,?…)
NotIn findByXXNotIn where XX not in (?,?…)
True findByXXTrue where XX = true
False findByXXFalse where XX = false
StartingWith findByXXStartingWith where XX like ‘:XX%’
EndingWith findByXXEndingWith where XX like ‘%:XX’

Using @EmbeddedId complicates naming conventions.

If you are using @EmbeddedId (compound primary key), the method name is a bit tricky.

If you want to use PK in an entity with a compound primary key like the following, you will have to add a field name with @EmbeddedId to the method name.

package jp.co.confrage;

import java.io.Serializable;

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

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Entity
@Table(name="employee_master")
public class Employee {
  @EmbeddedId
  private PrimaryK id;

  @Column(name="empname")
  private String empname;

  @Embeddable
  @Data
  @AllArgsConstructor
  @NoArgsConstructor
  public static class PrimaryK implements Serializable{
    private static final long serialVersionUID = -2523459362991270288L;

    @Column(name="id")
    private String id;

    @Column(name="empno")
    private String empno;
  }
}

For example, if you want to search by empno

public Employee findByEmpno(String empno); // error
public Employee findByIdEmpno(String empno); // correct

How to use native functions

If you want to use a function that is not provided in JPQL or depends on each RDB in JPQL, do the following

SELECT FUNCTION('function name', Argument 1, Argument 2…) ~

Sort class in org.springframework.data.domain.

It is possible to sort by passing the class org.springframework.data.domain.Sort without sorting by order by in the query statement. There is an enum named org.springframework.data.domain.sort.Direction, so ascending order for ASC and descending order for DESC. The second argument is the field name of the entity.

// caller
List<AccountEntity> list = accountRepository.findList(new Sort(Direction.ASC, "depositAmount"));

Below is the repository class, passing an instance of the Sort class without sorting by JPQL.

@Query(value="select m from AccountEntity m")
List<AccountEntity> findList(Sort sort);

Fetching large amounts of data using @QueryHints

The @QueryHints annotation can be used to adjust the fetch size.

The fetch size is specified as a string in the value attribute of @QueryHint.

For name, specify org.hibernate.jpa.QueryHints.HINT_FETCH_SIZE.

The following is an example of setting the fetch size to 1000.

@QueryHints(value=@QueryHint(name = org.hibernate.jpa.QueryHints.HINT_FETCH_SIZE, value="1000"))
@Query(value = "select m.empname from UserEntity m ")

public List<String> findByPk();

@EntityListeners(AuditingEntityListener.class) to set registrant, registration time, updater and update time

By annotating entities with the @EntityListeners(AuditingEntityListener.class) annotation, you can set the registrant, updater, registration time, and update time.

Annotate the property you wish to set.

annotation meaning class
@Version Version Integer and so on
@CreatedBy Registrant String
@CreatedDate Registration Date LocalDateTime
@LastModifiedBy Updated By String|
@LastModifiedDate ModifiedDate LocalDateTime

The above annotations are assigned to each property. The following is an example of an entity.

import java.io.Serializable;
import java.time.LocalDateTime;

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

import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Table(name="account")
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Data
@EntityListeners(AuditingEntityListener.class)
public class AccountEntity {
  @EmbeddedId
  private Pk aaa;

  @Column(name="branch_number")
  private Integer branchNumber;

  @Column(name="deposit_amount")
  private Integer depositAmount;

  @CreatedBy
  @Column(name="register_user")
  private String registerUser;

  @CreatedDate
  @Column(name="register_date")
  private LocalDateTime registerDate;

  @LastModifiedBy
  @Column(name="update_user")
  private String updateUser;

  @LastModifiedDate
  @Column(name="update_date")
  private LocalDateTime updateDate;

  @Embeddable
  @Data
  @AllArgsConstructor
  @NoArgsConstructor
  public static class Pk implements Serializable {
    private static final long serialVersionUID = 624797775027966843L;

    @Column(name="id")
    private String id;

    @Column(name="account_number")
    private String accountNumber;
  }
}

This alone will not register any audit information when inserted in the save method.

We need to create a configuration class org.springframework.data.domain.AuditorAware interface and create the following class.

Here, we use testuser as a sticky note.

import java.util.Optional;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing
public class AuditConfig {
  @Bean
  public AuditorAware<String> auditorAware() {
    return new AuditorAware<String>() {
      @Override
      public Optional<String> getCurrentAuditor() {
        return Optional.of("testuser");
      }
    };
  }
}

Now when you save, testuser will be automatically set as the registrant or updater, and the time will be automatically set as the registration time or update time.

If nativeQuery=true, they will not be set automatically.

Since audit information is information that any table has, it is better to create a parent class and write it there.

Here, we give the parent class the @MappedSuperclass annotation and extend it with each entity.

Here is an example of the parent class AbstractEntity class.

package jp.co.confrage.domain.entity;

import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractEntity {
  /** version. */
  @Version
  @Column(name = "version")
  private Integer version;

  /** author. */
  @CreatedBy
  @Column(name = "creator")
  private String creatorCode;

  /** creation-time. */
  @CreatedDate
  @Column(name = "create_time")
  private LocalDateTime createdTim;

  /** updater. */
  @LastModifiedBy
  @Column(name = "updater")
  private String updaterCode;

  /** Update Date. */
  @LastModifiedDate
  @Column(name = "update_time")
  private LocalDateTime updatedTim;
}
https://gist.github.com/takahashi-h5/907c808af2a58c78060e4f933df24743

Configure on DB side

In the case of MySQL, it is also possible to set this up automatically in the table.

How to automatically set registered and modified dates for MySQL tables

Describe JPQL in properties using the jpa-named-queries.properties file

Queries can be described in the properties file.

By default, the queries are described in META-INF/jpa-named-queries.properties.

The JPQL that describes the repository interface @Query(name=Sample.find) and maps to the value specified in this name attribute is described in META-INF/jpa-named-queries.properties.

jpa-named-queries.properties

Sample.find=\
SELECT m FROM Employee

Now you can write in META-INF/jpa-named-queries.properties.

Use the deleteInBatch method rather than the delete method

Spring Data JPA provides a delete method in advance, but when deleting multiple rows, a delete statement is issued for each row.

Therefore, it is better to use the deleteInBatch method for bulk delete.

Use the deleteAllInBatch method rather than the deleteAll method

The deleteAll method is also a pre-prepared method, but it deletes all records, but it only executes the delete statement for the number of records.

Therefore, it requires a lot of communication.

If you want to use the deleteAll method, there is a deleteAllInBatch method that allows bulk deletion.

jpql performance

There are various perspectives when it comes to performance, such as speed and memory consumption, but jpql consumes significantly less memory with nativeQuery=true.

With the default (nativeQuery=false), a Java instance is created, so if a large amount of data is retrieved, memory will be consumed all at once.

In the case of too much data, OOME will occur, so nativeQuery=true is recommended.

Here is a speed test. Reference Site

Paging process using org.springframework.data.domain.Page class

When retrieving large amounts of data (such as findAll), it is more convenient to use the Page class for paging, which limits the number of items displayed and allows page-by-page processing.

From a performance standpoint, memory usage is also considerably reduced.

argument meaning
first argument|0 as base value|pages
secondargument data units per page
Page<"ENTITTY"> paging = "REPOSITORY".findAll(PageRequest.of(0, 3));

The PageRequest class is instantiated using the of method, not new.

It seems that instantiation with new is deprecated.

Instances of the Page<entity> class have the following methods

return value method meaning
boolean hasContent Whether content is available or not
List getContent Content per page
int getNumber Current page number
int getSize Size per page
int getNumberOfElements Number of items currently retrieved by content
int getTotalElements Number of all records regardless of page
int getTotalPages Total number of pages

If you want to retrieve pages incrementally, you can write the following.

int i = 0; // Page Base Value
int size = 5; // Number of data per page
Page<AccountEntity> paging = accountRepository.findAll(PageRequest.of(i, size));
while(paging.hasContent()) {
  List<AccountEntity> list = paging.getContent();
  list.stream().forEach(e -> System.out.println(e.getAaa().getAccountNumber()));
  paging = accountRepository.findAll(PageRequest.of(++i, size));
}

Union,Union All not supported

Union,Union All is not supported by JPQL. if you set nativeQuery=true, you can execute SQL, but you have to do List.add on the program side.

No identifier specified for entity

This error occurs when @Id (javax.persistence.Id) is not specified for an entity.

One @Id is required for an entity.

 Not supported for DML operations

Not supported for DML operations” error occurs if @Modifying annotation is not attached when SQL for updating is issued.

@Modifying
@Query(value="update User m set m.name = 'takahashi'")
public void updateBySample();

limit,offset Although limit, offset, etc. can be used in PostgreSQL, JPQL does not support them, so the setFirstResult and setMaxResults methods of the TypedQuery interface can be used instead.

Or use the top,first keyword.

Spring Data JPA - Reference Documentation

column “xxx” is of type timestamp without time zone but expression is of type bytea

In PostgreSQL, native query update statement may cause “column “xxx” is of type timestamp without time zone but expression is of type bytea” error.

This is because the update statement is issued with a column of type timestamp(6) without time zone called “hoge” set to null, and if nativeQuery=false, the update is successfully performed with null.

コメント

Discover more from 株式会社CONFRAGE ITソリューション事業部

Subscribe now to keep reading and get access to the full archive.

Continue reading

Copied title and URL