Unit 3
Unit 3
Java Database Connectivity (or JDBC for short) is a Java API designed to connect and manage relational
databases in applications programmed with Java.
The JDBC API uses JDBC drivers to interact with a database. There are different types of JDBC drivers:
JDBC-ODBC bridge driver: Use JDBC-ODBC bridge driver for testing JDBC applications against
an ODBC (Open database connectivity) data source.
Native driver: Native driver requires a native database API.
Network driver protocol: Network driver protocol is common; it’s created for a particular vendor’s
database.
Spring Data JPA API provides JpaTemplate class to integrate spring application with JPA.
JPA (Java Persistent API) is the sun specification for persisting objects in the enterprise
application. It is currently used as the replacement for complex entity beans.
The implementation of JPA specification is provided by many vendors such as:
o Hibernate
o Toplink
o iBatis
o OpenJPA etc.
You don't need to write the before and after code for persisting, updating, deleting or searching
object such as creating Persistence instance, creating EntityManagerFactory instance, creating
EntityTransaction instance, creating EntityManager instance, commiting EntityTransaction
instance and closing EntityManager.
What is JPA?
Spring Boot JPA is a Java specification for managing relational data in Java applications. It
allows us to access and persist data between Java object/ class and relational database. JPA
follows Object-Relation Mapping (ORM). It is a set of interfaces. It also provides a
runtime EntityManager API for processing queries and transactions on the objects against the
database. It uses a platform-independent object-oriented query language JPQL (Java Persistent
Query Language).
JPA is not a framework. It defines a concept that can be implemented by any framework.
JPA is simpler, cleaner, and less labor-intensive than JDBC, SQL, and hand-written mapping.
JPA is suitable for non-performance oriented complex applications. The main advantage of JPA
over JDBC is that, in JPA, data is represented by objects and classes while in JDBC data is
represented by tables and records. It uses POJO to represent persistent data that simplifies
database programming. There are some other advantages of JPA:
o JPA avoids writing DDL in a database-specific dialect of SQL. Instead of this, it allows
mapping in XML or using Java annotations.
o JPA allows us to avoid writing DML in the database-specific dialect of SQL.
o JPA allows us to save and load Java objects and graphs without any DML language at
all.
o When we need to perform queries JPQL, it allows us to express the queries in terms of
Java entities rather than the (native) SQL table and columns.
JPA Features:
JPA Architecture:
JPA is a source to store business entities as relational entities. It shows how to define a POJO as
an entity and how to manage entities with relation.
The following figure describes the class-level architecture of JPA that describes the core classes
and interfaces of JPA that is defined in the javax persistence package. The JPA architecture
contains the following units:
1) Annotation-based Configuration:
The Spring Data JPA repositories support can be activated through both JavaConfig as well as a
custom XML namespace, as shown in the following example:
@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
class ApplicationConfig {
@Bean
public DataSource dataSource() {
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory
entityManagerFactory)
{
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
The preceding configuration class sets up an embedded HSQL database by using the
EmbeddedDatabaseBuilder API of spring-jdbc. Spring Data then sets up an
EntityManagerFactory and uses Hibernate as the sample persistence provider. The last
infrastructure component declared here is the JpaTransactionManager. Finally, the example
activates Spring Data JPA repositories by using the @EnableJpaRepositories annotation, which
essentially carries the same attributes as the XML namespace. If no base package is configured,
it uses the one in which the configuration class resides.
2) Spring Namespace
The JPA module of Spring Data contains a custom namespace that allows defining repository
beans. It also contains certain features and element attributes that are special to JPA. Generally,
the JPA repositories can be set up by using the repositories element, as shown in the following
example:
Which is better, JavaConfig or XML? XML is how Spring was configured long ago. In today’s
era of fast-growing Java, record types, annotations, and more, new projects typically use as
much pure Java as possible. While there is no immediate plan to remove XML support, some of
the newest features may not be available through XML.
Using the repositories element it activates persistence exception translation for all beans
annotated with @Repository, to let exceptions being thrown by the JPA persistence providers be
converted into Spring’s DataAccessException hierarchy.
Custom Namespace Attributes
Beyond the default attributes of the repositories element, the JPA namespace offers additional
attributes to let you gain more detailed control over the setup of the repositories:
Bootstrap Mode
By default, Spring Data JPA repositories are default Spring beans. They are singleton scoped
and eagerly initialized. During startup, they already interact with the JPA EntityManager for
verification and metadata analysis purposes. Spring Framework supports the initialization of the
JPA EntityManagerFactory in a background thread because that process usually takes up a
significant amount of startup time in a Spring application. To make use of that background
initialization effectively, we need to make sure that JPA repositories are initialized as late as
possible.
As of Spring Data JPA 2.1 you can now configure a BootstrapMode (either via the
@EnableJpaRepositories annotation or the XML namespace) that takes the following values:
DEFAULT (default) — Repositories are instantiated eagerly unless explicitly annotated with
@Lazy. The lazification only has effect if no client bean needs an instance of the repository as
that will require the initialization of the repository bean.
LAZY — Implicitly declares all repository beans lazy and also causes lazy initialization proxies
to be created to be injected into client beans. That means, that repositories will not get
instantiated if the client bean is simply storing the instance in a field and not making use of the
repository during initialization. Repository instances will be initialized and verified upon first
interaction with the repository.
DEFERRED — Fundamentally the same mode of operation as LAZY, but triggering repository
initialization in response to an ContextRefreshedEvent so that repositories are verified before the
application has completely started.
Pagination is often helpful when we have a large dataset and we want to present it to the user in
smaller chunks.
Also, we often need to sort that data by some criteria while paging.
Initial Setup
@Id
private long id;
private String name;
private double price;
Each of our Product instances has a unique identifier: id, its name and its price associated with
it.
Creating a Repository
Once we have our repository extending from PagingAndSortingRepository, we just need to:
We can create a PageRequest object by passing in the requested page number and the page size.
Here the page count starts at zero:
Pageable firstPageWithTwoElements = PageRequest.of(0, 2);
List<Product> allTenDollarProducts =
productRepository.findAllByPrice(10, secondPageWithFiveElements);Copy
The findAll(Pageable pageable) method by default returns a Page<T> object.
However, we can choose to return either a Page<T>, a Slice<T>, or a List<T> from any of
our custom methods returning paginated data.
A Page<T> instance, in addition to having the list of Products, also knows about the total
number of available pages. It triggers an additional count query to achieve it. To avoid such
an overhead cost, we can instead return a Slice<T> or a List<T>.
A Slice only knows whether the next slice is available or not.
Similarly, to just have our query results sorted, we can simply pass an instance of Sort to the
method:
Page<Product> allProductsSortedByName = productRepository.findAll(Sort.by("name"));Copy
However, what if we want to both sort and page our data?
We can do that by passing the sorting details into our PageRequest object itself:
Pageable sortedByName =
PageRequest.of(0, 3, Sort.by("name"));
Pageable sortedByPriceDesc =
PageRequest.of(0, 3, Sort.by("price").descending());
Pageable sortedByPriceDescNameAsc =
PageRequest.of(0, 5, Sort.by("price").descending().and(Sort.by("name")));Copy
Based on our sorting requirements, we can specify the sort fields and the sort direction while
creating our PageRequest instance.
As usual, we can then pass this Pageable type instance to the repository’s method.
Query methods are methods that find information from the database and are declared on the
repository interface. For example, if we want to create a database query that finds
the Todo object that has a specific id, we can create the query method by adding
the findById() method to the TodoRepository interface. After we have done this, our repository
interface looks as follows:
1 import org.springframework.data.repository.Repository;
2
3 interface TodoRepository extends Repository<Todo, Long> {
4
5 //This is a query method.
6 Todo findById(Long id);
7 }
A query method can return only one result or more than one result. Also, we can create a query
method that is invoked asynchronously. This section addresses each of these situations and
describes what kind of return values we can use in each situation.
First, if we are writing a query that should return only one result, we can return the following
types:
Basic type. Our query method will return the found basic type or null.
Entity. Our query method will return an entity object or null.
Guava / Java 8 Optional<T>. Our query method will return an Optional that contains the
found object or an empty Optional.
Here are some examples of query methods that return only one result:
1 import java.util.Optional;
2 import org.springframework.data.jpa.repository.Query;
3 import org.springframework.data.repository.Repository;
4 import org.springframework.data.repository.query.Param;
5
6 interface TodoRepository extends Repository<Todo, Long> {
7
8 @Query("SELECT t.title FROM Todo t where t.id = :id")
9 String findTitleById(@Param("id") Long id);
10
11 @Query("SELECT t.title FROM Todo t where t.id = :id")
12 Optional<String> findTitleById(@Param("id") Long id);
13
14 Todo findById(Long id);
15
16 Optional<Todo> findById(Long id);
17 }
Second, if we are writing a query method that should return more than one result, we can return
the following types:
List<T>. Our query method will return a list that contains the query results or an empty list.
Stream<T>. Our query method will return a Stream that can be used to access the query
results or an empty Stream.
Here are some examples of query methods that return more than one result:
1 import java.util.stream.Stream;
2 import org.springframework.data.repository.Repository;
3
4 interface TodoRepository extends Repository<Todo, Long> {
5
6 List<Todo> findByTitle(String title);
7
8 Stream<Todo> findByTitle(String title);
9 }
Third, if we want that our query method is executed asynchronously, we have to annotate it with
the @Async annotation and return a Future<T> object. Here are some examples of query
methods that are executed asynchronously:
1 import java.util.concurrent.Future;
2 import java.util.stream.Stream;
3 import org.springframework.data.jpa.repository.Query;
4 import org.springframework.data.repository.Repository;
5 import org.springframework.data.repository.query.Param;
6 import org.springframework.scheduling.annotation.Async;
7
8 interface TodoRepository extends Repository<Todo, Long> {
9
10 @Async
11 @Query("SELECT t.title FROM Todo t where t.id = :id")
12 Future<String> findTitleById(@Param("id") Long id);
13
14 @Async
15 @Query("SELECT t.title FROM Todo t where t.id = :id")
16 Future<Optional<String>> findTitleById(@Param("id") Long id);
17
18 @Async
Future<Todo> findById(Long id);
19 @Async
20 Future<Optional<Todo>> findById(Long id);
21
22 @Async
23 Future<List<Todo>> findByTitle(String title);
24
25 @Async
Future<Stream<Todo>> findByTitle(String title);
}
We can pass parameters to our database queries by passing method parameters to our query
methods. Spring Data JPA supports both position based parameter binding and named
parameters. Both of these options are described in the following.
The position based parameter binding means that the order of our method parameters decides
which placeholders are replaced with them. In other words, the first placeholder is replaced with
the first method parameter, the second placeholder is replaced with the second method
parameter, and so on.
Here are some query methods that use the position based parameter binding:
1 import java.util.Optional
2 import org.springframework.data.jpa.repository.Query;
3 import org.springframework.data.repository.Repository;
4
5
6 interface TodoRepository extends Repository<Todo, Long> {
7
8 public Optional<Todo> findByTitleAndDescription(String title, String description);
9
10 @Query("SELECT t FROM Todo t where t.title = ?1 AND t.description = ?2")
11 public Optional<Todo> findByTitleAndDescription(String title, String description);
12
13 @Query(value = "SELECT * FROM todos t where t.title = ?0 AND t.description = ?1",
14 nativeQuery=true
15 )
16 public Optional<Todo> findByTitleAndDescription(String title, String description);
17 }
Using position based parameter binding is a bit error prone because we cannot change the order
of the method parameters or the order of the placeholders without breaking our database query.
We can solve this problem by using named parameters.
We can use named parameters by replacing the numeric placeholders found from our database
queries with concrete parameter names, and annotating our method parameters with
the @Param annotation.
The @Param annotation configures the name of the named parameter that is replaced with the
value of the method parameter.
1 import java.util.Optional
2 import org.springframework.data.jpa.repository.Query;
3 import org.springframework.data.repository.Repository;
4 import org.springframework.data.repository.query.Param;
5
6
7 interface TodoRepository extends Repository<Todo, Long> {
8
9 @Query("SELECT t FROM Todo t where t.title = :title AND t.description = :description")
10 public Optional<Todo> findByTitleAndDescription(@Param("title") String title,
11 @Param("description") String description);
12
13 @Query(
14 value = "SELECT * FROM todos t where t.title = :title AND t.description = :description",
15 nativeQuery=true
16 )
17 public Optional<Todo> findByTitleAndDescription(@Param("title") String title,
18 @Param("description") String description);
19 }
Some time case arises, where we need a custom query to fulfil one test case. We can
use @NamedQuery annotation to specify a named query within an entity class and
then declare that method in repository. Following is an example.
We've added custom methods in Repository in JPA Custom Methods chapter. Now
let's add another method using @NamedQuery and test it.
Entity - Entity.java
Following is the default code of Employee. It represents a Employee table with id,
name, age and email columns.
package com.tutorialspoint.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
@Entity
@Table
@NamedQuery(name = "Employee.findByEmail",
query = "select e from Employee e where e.email = ?1")
public class Employee {
@Id
@Column
private int id;
@Column
private String name;
@Column
private int age;
@Column
private String email;
Repository - EmployeeRepository.java
Add a method to find an employee by its name and age.
package com.tutorialspoint.repository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.tutorialspoint.entity.Employee;
@Repository
public interface EmployeeRepository extends CrudRepository<Employee, Integer> {
public List<Employee> findByName(String name);
public List<Employee> findByAge(int age);
public Employee findByEmail(String email);
}
Now Spring JPA will create the implementation of above methods automatically
using the query provided in named query. Let's test the methods added by adding their
test cases in test file. Last two methods of below file tests the named query method
added.
package com.tutorialspoint.repository;
@ExtendWith(SpringExtension.class)
@Transactional
@SpringBootTest(classes = SprintBootH2Application.class)
public class EmployeeRepositoryTest {
@Autowired
private EmployeeRepository employeeRepository;
@Test
public void testFindById() {
Employee employee = getEmployee();
employeeRepository.save(employee);
Employee result = employeeRepository.findById(employee.getId()).get();
assertEquals(employee.getId(), result.getId());
}
@Test
public void testFindAll() {
Employee employee = getEmployee();
employeeRepository.save(employee);
List<Employee> result = new ArrayList<>();
employeeRepository.findAll().forEach(e -> result.add(e));
assertEquals(result.size(), 1);
}
@Test
public void testSave() {
Employee employee = getEmployee();
employeeRepository.save(employee);
Employee found = employeeRepository.findById(employee.getId()).get();
assertEquals(employee.getId(), found.getId());
}
@Test
public void testDeleteById() {
Employee employee = getEmployee();
employeeRepository.save(employee);
employeeRepository.deleteById(employee.getId());
List<Employee> result = new ArrayList<>();
employeeRepository.findAll().forEach(e -> result.add(e));
assertEquals(result.size(), 0);
}
private Employee getEmployee() {
Employee employee = new Employee();
employee.setId(1);
employee.setName("Mahesh");
employee.setAge(30);
employee.setEmail("[email protected]");
return employee;
}
@Test
public void testFindByName() {
Employee employee = getEmployee();
employeeRepository.save(employee);
List<Employee> result = new ArrayList<>();
employeeRepository.findByName(employee.getName()).forEach(e ->
result.add(e));
assertEquals(result.size(), 1);
}
@Test
public void testFindByAge() {
Employee employee = getEmployee();
employeeRepository.save(employee);
List<Employee> result = new ArrayList<>();
employeeRepository.findByAge(employee.getAge()).forEach(e -> result.add(e));
assertEquals(result.size(), 1);
}
@Test
public void testFindByEmail() {
Employee employee = getEmployee();
employeeRepository.save(employee);
Employee result = employeeRepository.findByEmail(employee.getEmail());
assertNotNull(result);
}
}
1. Overview
This tutorial will discuss the right way to configure Spring Transactions, how to use
the @Transactional annotation and common pitfalls.
For a more in-depth discussion on the core persistence configuration, check out the Spring with
JPA tutorial.
Basically, there are two distinct ways to configure Transactions, annotations and AOP, each with
its own advantages. We’re going to discuss the more common annotation config here.
2. Configure Transactions
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
//...
}
@Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
}Copy
However, if we’re using a Spring Boot project and have a spring-data-* or spring-tx
dependencies on the classpath, then transaction management will be enabled by default.
For versions before 3.1, or if Java is not an option, here is the XML configuration
using annotation-driven and namespace support:
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="myEmf" />
</bean>
<tx:annotation-driven transaction-manager="txManager" />Copy
With transactions configured, we can now annotate a bean with @Transactional either at the
class or method level:
@Service
@Transactional
public class FooService {
//...
}Copy
The annotation supports further configuration as well:
Note that by default, rollback happens for runtime, unchecked exceptions only. The checked
exception does not trigger a rollback of the transaction. We can, of course, configure this
behavior with the rollbackFor and noRollbackFor annotation parameters.
Declarative Transaction Management
Most Spring Framework users choose declarative transaction management. This option has the least impact o
with the ideals of a non-invasive lightweight container.
The Spring Framework’s declarative transaction management is made possible with Spring
aspect-oriented programming (AOP). However, as the transactional aspects code comes with the
Spring Framework distribution and may be used in a boilerplate fashion, AOP concepts do not
generally have to be understood to make effective use of this code.
The Spring Framework’s declarative transaction management is similar to EJB CMT, in that you
can specify transaction behavior (or lack of it) down to the individual method level. You can
make a setRollbackOnly() call within a transaction context, if necessary. The differences
between the two types of transaction management are:
Unlike EJB CMT, which is tied to JTA, the Spring Framework’s declarative transaction
management works in any environment. It can work with JTA transactions or local
transactions by using JDBC, JPA, or Hibernate by adjusting the configuration files.
You can apply the Spring Framework declarative transaction management to any class,
not merely special classes such as EJBs.
The Spring Framework offers declarative rollback rules, a feature with no EJB
equivalent. Both programmatic and declarative support for rollback rules is provided.
The Spring Framework lets you customize transactional behavior by using AOP. For
example, you can insert custom behavior in the case of transaction rollback. You can also
add arbitrary advice, along with transactional advice. With EJB CMT, you cannot
influence the container’s transaction management, except with setRollbackOnly().
The Spring Framework does not support propagation of transaction contexts across
remote calls, as high-end application servers do. If you need this feature, we recommend
that you use EJB. However, consider carefully before using such a feature, because,
normally, one does not want transactions to span remote calls.
The concept of rollback rules is important. They let you specify which exceptions (and
throwables) should cause automatic rollback. You can specify this declaratively, in
configuration, not in Java code. So, although you can still call setRollbackOnly() on
the TransactionStatus object to roll back the current transaction back, most often you can specify
a rule that MyApplicationException must always result in rollback. The significant advantage to
this option is that business objects do not depend on the transaction infrastructure. For example,
they typically do not need to import Spring transaction APIs or other Spring APIs.
Although EJB container default behavior automatically rolls back the transaction on a system
exception (usually a runtime exception), EJB CMT does not roll back the transaction
automatically on an application exception (that is, a checked exception other
than java.rmi.RemoteException). While the Spring default behavior for declarative transaction
management follows EJB convention (roll back is automatic only on unchecked exceptions), it is
often useful to customize this behavior.
1. Retrieve the entity object that you want to update. You can do this using
the findById() method of the JpaRepository interface.
3. Save the entity back to the database by calling the save() method of
the JpaRepository interface.
Here's an example:
if (optionalUser.isPresent()) {
user.setUsername("newUsername");
user.setPassword("newPassword");
userRepository.save(user);
Note that the save() method will perform an update if the entity already exists in the
database, and an insert if it does not.
Follow @vlad_mihalcea
Imagine having a tool that can automatically detect JPA and Hibernate performance
issues. Wouldn’t that be just awesome?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring
Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.
So, enjoy spending your time on the things you love rather than fixing performance
issues in your production system on a Saturday night!
Introduction
In this article, I’m going to show you the best way to write a custom Spring Data Repository.
While the default JpaRepository methods, as well as the query methods, are very
convenient in many situations, there might be times when you need custom Repository
methods that can take advantage of any JPA provider-specific functionality.
p.title as p_title,
pc.id as pc_id,
pc.review as pc_review
from PostComment pc
join pc.post p
order by pc.id
""")
.unwrap(org.hibernate.query.Query.class)
.setResultTransformer(new PostDTOResultTransformer())
.getResultList();
List<PostDTO> findPostDTOWithComments();
@PersistenceContext
@Override
return entityManager.createNativeQuery("""
p.title AS p_title,
pc.id AS pc_id,
pc.review AS pc_review
FROM post p
ORDER BY pc.id
""")
.unwrap(org.hibernate.query.Query.class)
.setResultTransformer(new PostDTOResultTransformer())
.getResultList();
Third, we need to make the default Spring Data JPA PostRepository extend
our CustomPostRepository interface:
@Repository
public interface PostRepository
A picture is worth 100 words, so here’s a diagram that shows you how the custom
Spring Data Repository is associated to the standard JpaRepository one:
Testing time
Assuming we have two Post entities, the first one having two PostComment child entities,
and the second Post having a single PostComment child:
entityManager.persist(
new Post()
.setId(1L)
.addComment(
new PostComment()
.setId(1L)
.addComment(
new PostComment()
.setId(2L)
);
entityManager.persist(
new Post()
.setId(2L)
.setTitle("Hypersistence Optimizer")
.addComment(
new PostComment()
.setId(3L)
);
assertEquals(2, postDTOs.size());
assertEquals(2, postDTOs.get(0).getComments().size());
assertEquals(1, postDTOs.get(1).getComments().size());
assertEquals(1L, post1DTO.getId().longValue());
assertEquals(2, post1DTO.getComments().size());
assertEquals(1L, post1DTO.getComments().get(0).getId().longValue());
assertEquals(2L, post1DTO.getComments().get(1).getId().longValue());
assertEquals(2L, post2DTO.getId().longValue());
assertEquals(1, post2DTO.getComments().size());
assertEquals(3L, post2DTO.getComments().get(0).getId().longValue());