Spring JpaRepository
Spring Data JPA makes it possible to search data very easily by using a feature called JpaRepository. This article explains its basic usage.
Create the JpaRepository Interface
Previously, we briefly covered basic database access. At that time, we explained that queries called JPQL could be used to search data. Do you remember that?
Many people who read that explanation probably sighed without realizing it. You may have started using a framework because you did not want to see SQL, only to find out that something like SQL still existed.
Still, you can relax. The Spring Framework provides a structure for searching data more intelligently. That structure is called JpaRepository.
JpaRepository is an interface. By defining search methods in the interface in advance, you can perform smart data searches simply by calling methods. If you are thinking, “In the end, those methods probably contain JPQL search logic,” hold on a little longer. It will be worth it.
First, create the JpaRepository interface. In the com.devkuma.spring.db package, create a source file named SampleEntityRepository.java. Then write the interface as follows.
package com.devkuma.spring.db;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface SampleEntityRepository
extends JpaRepository <SampleEntity, Long> {
}
The JpaRepository interface is created by extending the JpaRepository interface in the org.springframework.data.jpa.repository package. This interface is used generically. Looking at the code, you can see it is written in the following form.
public interface Name extends JpaRepository <Entity, ID type>
Inside <>, specify the entity class name and the type of the ID field. The important point is that, for primitive types, you specify the wrapper class. The sample SampleEntity class uses a long type for the ID, so here it is written as <SampleEntity, Long>.
Another point to note is the annotation. The declaration has the @Repository annotation before it. This indicates that the interface is a JpaRepository. Be sure to add it.
Prepare the Bean Configuration File
Now prepare the bean configuration file. A file named dbbean.xml has already been prepared, so we will reuse it as is.
Rewrite the contents of dbbean.xml as follows.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<jdbc:embedded-database id="dataSource" type="H2" />
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="true" />
</bean>
</property>
</bean>
<jpa:repositories base-package="com.devkuma.spring.db" />
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
</beans>
This time, two bean-related tags have been added. First, there is a tag for JpaRepository.
<jpa:repositories base-package="com.devkuma.spring.db"/>
This <jpa:repositories> tag includes an attribute named base-package. It specifies the location, or package, to search. With this, Spring searches the specified package for JpaRepository interfaces and turns them into beans.
The other addition is a bean called a transaction manager. It manages transactions and is written as follows.
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
This defines a bean named transactionManager, and its class is JpaTransactionManager. It has another property named entityManagerFactory, which specifies the EntityManagerFactory used by this bean. The id="entityManagerFactory" bean above is specified with ref.
This completes the bean preparation required to use JpaRepository. You may wonder, “Do I not need to prepare SampleEntityRepository as a bean?” It is not necessary. <jpa:repositories> searches the specified package and automatically registers JpaRepository interfaces marked with @Repository as beans. Therefore, there is no need to prepare a separate <bean>.
Use JpaRepository in the Application
Now let’s use JpaRepository.
Modify the App class in the com.devkuma.spring.db package as follows.
package com.devkuma.spring.db;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
private static EntityManager manager;
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("dbbean.xml");
EntityManagerFactory factory = app.getBean(EntityManagerFactory.class);
manager = factory.createEntityManager();
SampleEntityRepository repository = app.getBean(SampleEntityRepository.class);
// make dummy
makeDummyData();
// get list
List list = repository.findAll();
printList(list);
System.out.println("...ok.");
}
// create dummy entity data.
public static void makeDummyData() {
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
manager.persist(new SampleEntity("tuyano", "syoda@tuyano.com"));
manager.persist(new SampleEntity("hanako", "hanako@flower"));
manager.persist(new SampleEntity("taro", "taro@yamada"));
manager.persist(new SampleEntity("sachiko", "sachico@happy"));
manager.flush();
transaction.commit();
}
// print all entity.
public static void printList(List list) {
for (Object item : list) {
System.out.println(item);
}
}
}
Run it. All entities created as dummy data will be displayed.
Here, first obtain a SampleEntityRepository instance from the ApplicationContext.
SampleEntityRepository repository =
app.getBean (SampleEntityRepository.class);
This retrieves the bean. Although dbbean.xml does not contain a bean definition for it, the bean can be retrieved properly in this way. This is also thanks to @Repository.
List list = repository.findAll ();
Then call the findAll method to search the list of entities. Some readers may wonder where this method is defined. It is available because JpaRepository, inherited by SampleEntityRepository, provides it.
Where Is the Class?
Looking at this code, some people may find something strange. SampleEntityRepository is an interface, not a class. Yet it can naturally be obtained as a bean.
With JpaRepository, simply preparing the interface automatically creates a class and generates a bean. All you need is the interface.
Use a Bean Configuration Class
Beans can be defined with a class instead of a configuration file. Let’s try that as well.
Modify the source code of the SampleEntityConfig class in the com.devkuma.spring.db package as follows.
package com.devkuma.spring.db;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
@Configuration
@EnableJpaRepositories("com.tuyano.libro.db")
@EnableTransactionManagement
class SampleEntityConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.H2).build();
}
@Bean
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new
LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.tuyano.libro.db");
factory.setDataSource(dataSource());
factory.afterPropertiesSet();
return factory.getObject();
}
@Bean
protected JpaTransactionManager transactionManager
(EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Then modify the following statement in App so that it uses this SampleEntityConfig class instead of the bean configuration file.
ApplicationContext app = new ClassPathXmlApplicationContext("dbbean.xml");
↓
ApplicationContext app = new AnnotationConfigApplicationContext(SampleEntityConfig.class);
After modifying it, run App. As before, the dummy data is listed. The first thing to notice here is the annotation before the class declaration.
@EnableJpaRepositories("com.devkuma.spring.db")
This enables JpaRepository. By writing this annotation, the specified package is searched, and classes annotated with @Repository are registered as beans. The package to search must be specified inside the parentheses. You can think of it as corresponding to <jpa:repositories>.
@EnableTransactionManagement
This is for using transaction management. Preparing it enables the transaction manager.
Among the methods for bean creation annotated with @Bean, a method for JpaTransactionManager has been added. It creates and returns a JpaTransactionManager instance. As you can see, it simply returns new JpaTransactionManager. An EntityManagerFactory instance is passed as the argument.
As you can see, none of the prepared bean creation methods are for creating the repository. The repository is automatically turned into a bean.
Standard JpaRepository Methods
Here, we prepared the SampleEntityRepository interface, but its contents were empty. Even so, we could call the findAll method and search all entities.
The inherited JpaRepository defines several standard methods. You can perform basic database operations by using them first. Here are the main methods in brief.
findAll()
This has already appeared. It returns a List containing all entities.
getOne("ID")
Obtains one entity by specifying an ID. The argument contains a value of the type specified for that entity’s ID.
saveAndFlush(entity)
Saves the entity specified as the argument to the database.
delete("ID")
Deletes the entity with the ID specified as the argument from the database.
count()
Returns the number of entities as an int value.
For searching, only findAll is available by default, but methods for saving and deleting entities are also provided. These methods do not require you to start transaction processing manually. You can save or remove data simply by calling the methods.
Add Methods to JpaRepository
The heart of a database is, above all, searching. A database becomes powerful when it can search data by specifying ranges and conditions as needed.
However, JpaRepository has only findAll as a search-related method. You may think this is not enough. In fact, the true power of JpaRepository lies in searching. By adding search methods to the interface, you can add the search methods you need.
Let’s try it. Modify the SampleEntityRepository interface as follows.
package com.devkuma.spring.db;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface SampleEntityRepository
extends JpaRepository<SampleEntity, Long> {
public List<SampleEntity> findByNameLike(String name);
public List<SampleEntity> findByMailEndingWith(String mail);
}
Here, the following two statements were added.
public List <SampleEntity> findByNameLike(String name);
public List <SampleEntity> findByMailEndingWith(String mail);
The first, findByNameLike, performs a like search using the name value. The second, findByMailEndingWith, searches for entities whose mail value ends with the specified text.
Only method declarations were written, but the method names are important. Do not change them arbitrarily. Be sure to declare the methods with the names shown here. These method names have meaning. If you change them, they will not work properly.
Use JpaRepository Methods
Now modify App.java as follows.
package com.tuyano.libro.db;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
private static EntityManager manager;
public static void main(String[] args) {
//ApplicationContext app = new
AnnotationConfigApplicationContext(SampleEntityConfig.class);
ApplicationContext app = new ClassPathXmlApplicationContext("dbbean.xml");
EntityManagerFactory factory = app.getBean(EntityManagerFactory.class);
manager = factory.createEntityManager();
makeDummyData();
// get repository
SampleEntityRepository repository = app.getBean(SampleEntityRepository.class);
// get list
List list1 = repository.findByNameLike("%ko");
System.out.println("*Find By Name*");
printList(list1);
List list2 = repository.findByMailEndingWith(".com");
System.out.println("*Find By Mail*");
printList(list2);
System.out.println("...ok.");
}
// create dummy entity data.
public static void makeDummyData() {
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
manager.persist(new SampleEntity("tuyano", "syoda@tuyano.com"));
manager.persist(new SampleEntity("hanako", "hanako@flower.uk"));
manager.persist(new SampleEntity("taro", "taro@yamada.jp"));
manager.persist(new SampleEntity("sachiko", "sachico@happy.com"));
manager.flush();
transaction.commit();
}
// print all entity.
public static void printList(List list) { ...omitted... }
}
Looking at the code, you can see that the required entities can be searched easily.
Search by name
List list1 = repository.findByNameLike("%ko");
Search result:
SampleEntity [id=2, name=hanako, mail=hanako@flower.uk]
SampleEntity [id=4, name=sachiko, mail=sachico@happy.com]
Search by mail
List list2 = repository.findByMailEndingWith(".com");
Search result:
SampleEntity [id=1, name=tuyano, mail=syoda@tuyano.com]
SampleEntity [id=4, name=sachiko, mail=sachico@happy.com]
findByNameLike("%ko") searches for entities whose name value ends with ko. Also, findByMailEndingWith("com") searches for entities whose mail value ends with .com. You can see that only the required data is retrieved.
The point to pay close attention to is that SampleEntityRepository only declares the methods. In other words, no actual processing performed by these methods is written at all. Simply declaring the methods automatically creates methods that work properly.
This is the power of JpaRepository. When you write method names in a predefined format, JpaRepository automatically generates methods that actually work based on those names.
JpaRepository Method Naming Rules
If you know how to write method names for JpaRepository, you can quickly write and add the methods you need. No processing code is required at all. You only need method declarations. However, the method names must follow predefined rules. By naming methods appropriately according to those rules, methods are automatically generated.
Here is a brief summary of the naming rules.
- findByXX
The basic form is this. Add the entity property name after
findBy. The first letter of the property name is capitalized. For example, if searching byname, usefindByName; if searching bymail, usefindByMail.
After that, continue writing after the basic findByXX form.
- Like / NotLike
These are for fuzzy searches. AddingLikesearches for entities that contain the text specified as the argument. AddingNotLikesearches for entities that do not contain the argument text.findByNameLikeperforms a fuzzy search onnameusing the argument text. - StartingWith / EndingWith
These search for text values that start or end with the text specified as the argument.findByNameStartingWith("A")searches for items whosenamevalue starts withA. - IsNull / IsNotNull
These search for values that are null or not null. No arguments are needed.findByNameIsNull()searches only for items whosenamevalue is null. - True / False
These search for boolean values that are true or false. No arguments are needed.findByCheckTrue()searches only for items whosecheckfield is true. - Before / After
These are used with date or time values. They search for values before or after the value specified as the argument.findByCreateBefore(new Date())searches only for items whosecreatevalue is before the current time, assumingcreateis aDate. - LessThan / GreaterThan
These are used with numeric values. They search for items whose value is less than or greater than the argument.
findByAgeLessThan(20)searches for items whoseagevalue is less than 20. - Between
This takes two values as arguments and searches for items between those two values. For example,
findByAgeBetween(10, 20)searches for items whoseagevalue is between 10 and 20 inclusive. It can be used not only for numeric values but also for date and time fields.
There are more rules, but if you can use these, you can handle most basic searches. Once you can use JpaRepository well, you can perform various data searches without writing much code. It is very simple, so try different variations based on the sample.