一篇文章带你了解什么是 Spring JPA
什么是JDBC
JDBC(Java DataBase Connectivity),java数据库连接是用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由用Java语言编写的类和接口组成。
什么是JDBCTemplate
Template的字面意思是模板,Spring框架为我们提供了JDBCTemplate,它是访问数据库的类库,是Spring对JDBC的封装。
通俗点说就是Spring对jdbc的封装的模板
注意,JDBCTemplate不是ORM框架
JDBCTemplate如何使用
UserDao .java
@Repository
public class UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
public int addUser(User user){
return jdbcTemplate.update("INSERT INTO sys_user(username,jobs,phone) VALUE (?,?,?)",
user.getName,user.getJobs,user.getPhone);
}
public int updateUser(User user){
return jdbcTemplate.update("UPDATE sys_user SET username=?,jobs=?,phone=? WHERE id=?",
user.getName,user.getJobs,user.getPhone,user.getId);
}
public int deleteUser(Integer id){
return jdbcTemplate.update("DELETE FROM sys_user WHERE id=?",id);
}
public User getUserById(Integer id){
return jdbcTemplate.queryForObject("SELECT * FROM sys_user WHERE id =?",new BeanPropertyRowMapper<>(User.class),id);
}
}
UserService
@Service
public class UserService {
@Autowired
UserDao userDao;
public int addUser(User user){
return userDao.addUser(user);
}
public int updateUser(User user){
return userDao.updateUser(user);
}
public int deleteUser(Integer id){
return userDao.deleteUser(id);
}
public User getUserById(Integer id){
return userDao.getUserById(id);
}
}
这是一个简单的JDBCTemplate,controller调用service,然后service调用userDao类中的方法,从而操作数据库。
可以看到,SQL语句和java代码高度耦合,不宜维护,而且可读性也不高。
Mybatis改造
我们将以上的方式切换到 Mybatis。
首先删掉UserDao,添加mapper映射文件UserMapper ,
@Mapper
public interface UserMapper{
int addUser(User user);
int deleteUser(int id);
int updateUser(User user);
User getUserById(Integer id);
}
然后在指定的目录下,生成UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.layman.mapper.UserMapper">
<sql id="selectUserVo">
select d.username, d.jobs, d.phone from sys_user d
</sql>
<select id="getUserById" parameterType="Long" resultType="User">
<include refid="selectUserVo"/>
WHERE id = = #{id}
</select>
<insert id="addUser" parameterType="User">
insert into sys_user (
<if test="username!= null and username!= 0">username,</if>
<if test="jobs!= null and jobs!= 0">jobs,</if>
<if test="phone!= null and phone!= ''">phone,</if>
create_time
)values(
<if test="username!= null and username!= 0">#{username},</if>
<if test="jobs!= null and jobs!= 0">#{jobs},</if>
<if test="phone!= null and phone!= ''">#{phone},</if>
sysdate()
)
</insert>
<delete id="deleteUser">
delete from sys_user where id= #{id}
</delete>
</mapper>
最后改造Service
@Service
public class UserService {
@Autowired
UserMapper userMapper;
public int addUser(User user){
return userMapper.addUser(user);
}
public int updateUser(User user){
return userMapper.updateUser(user);
}
public int deleteUser(Integer id){
return userMapper.deleteUser(id);
}
public User getUserById(Integer id){
return userMapper.getUserById(id);
}
}
可以看到,这样改造之后,SQL语句和业务代码分离,解耦,代码可读性和扩展性也得到提升。
但是,这样的改造之后,随之而来,暴露了问题。
如果要实现多个表的CRUD,就需要定义不同的实体类,然后写对应的mapper文件,然后写雷同的CRUD方法。这显然是繁琐重复且没有技术含量的操作
JPA的概念
JPA (Java Persistence API) 是 Sun 官方提出的 Java 持久化规范。它为 Java 开发人员提供了一种对象关系映射工具来管理 Java 应用中的数据。
它的出现,主要是为了简化现有的持久化开发工作,以及整合 ORM 技术,结束现在 Hibernate,TopLink,Mybatis等很多ORM 框架各自为营的凌乱局面。
JPA 在充分吸收了现有Hibernate,TopLink,JDO 等ORM框架的基础上发展而来的,具有易于使用,伸缩性强等优点。
换言之,JPA 是规范,而 Hibernate,TopLink,Mybatis则实现了 JPA 规范。
如何配置
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.5.5</version>
</dependency>
application.properties
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/layman?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=create
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
-
spring.jpa.properties.hibernate.hbm2ddl.auto
表示是否自动建表,如果不配置,表示禁用自动建表功能。它有以下4种配置:-
create
:每次加载Hibernate时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。 -
create-drop
:每次加载Hibernate时都会生成表,但当SessionFactory关闭时,所生成的表将自动删除。 -
update
:(最常用的配置
),第一次加载Hibernate时创建数据表(前提是需要先有数据库),以后加载Hibernate时不会删除上一次生成的表,会根据实体类进行更新,只新增字段,不会删除字段(即使实体中已经删除)。 -
validate
:每次加载Hibernate时都会验证数据表结构,只会和已经存在的数据表进行比较, 根据实体类修改表结构,但不会创建新表。
-
-
spring.jpa.show-sql=true
: 在执行数据库操作,控制台打印 sql 语句,方便我们检查。 -
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
:数据库的方言配置
新建用户实体类
import lombok.Data;
import javax.persistence.*;
/**
* @author layman
* @description: 用户实体类
* @date 2021/10/29
*/
@Data
@Entity(name = "sys_user")
public class User {
@Id
@GeneratedValue
private long id;
@Column(name = "sys_user_name",nullable = false,unique = true,length = 35)
private String userName;
@Transient
private Integer age;
// 头发数量(没办法,老天嫉妒我英俊的容颜,所以不给我头发)
@Column(name = "sys_hairnumber",is)
private Integer hairNumber;
// 是否有女朋友(别想了,肯定没有)
@Column(name = "sys_girlfriend")
private boolean hasGirlFriend;
}
@Entity注解
表示 该实体类会和数据库中的表建立关联关系,首次启动项目时,默认会在数据库中生成具有相同名称的的表,可以通过注解中的 name 属性来修改表名称, 如@Entity(name=“sys_user”)
, 这样数据库中表的名称则是 sys_user。该注解非常重要,如果没有该注解,首次启动项目时,数据库不会生成对应的表。@Table
注解可以用来修改表的名字,该注解完全可以忽略不用,因为@Entity 注解已具备该注解的功能。@Id
注解表明该属性字段是主键,该属性必须具备,否则报错。@GeneratedValue
该注解通常和 @Id 主键注解一起使用,用来定义主键的生成策略,该注解有多种策略,先总结如下:
主键策略 | 说明 |
---|---|
@GeneratedValue(strategy= GenerationType.IDENTITY) | 主键自增,由数据库自动生成,在 MySQL中使用最频繁,Oracle不支持 |
@GeneratedValue(strategy= GenerationType.AUTO) | 主键由程序控制,是 默认 的主键生成策略,oracle 默认是序列化,mysql 默认是主键自增 |
@GeneratedValue(strategy= GenerationType.AUTO) | 根据底层数据库的序列来生成主键,Oracle支持,MySQL不支持 |
@GeneratedValue(strategy= GenerationType.TABLE) | 使用一个特定的数据库表格来保存主键,较少使用 |
以 MySQL 数据库 为例, IDENTITY 和 AUTO 用的较多,其中 IDENTITY用的更多一些。
补充
-
@Column
是属性注解,该注解可以定义一个字段映射到数据库属性的具体特征,比如字段长度,映射到数据库时属性的具体名字等。 -
@Transient
是属性注解,该注解标注的字段 不会被映射到数据库 中。
声明UserRepository
该类的声明 必须继承 JpaRepository
package com.banana.springcloud.test;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @author layman
* @description: UserRepository
* @date 2021/10/29
*/
public interface UserRepository extends JpaRepository<User, Long> {
}
UML类图
可以看到 JpaRepository
继承了 CrudRepository
,后者定义了很多基本的Crud方法,可以拿来直接用。
实际工作的类是 SimpleJpaRepository
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID>{
}
简单实例
package com.banana.springcloud.test;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author layman
* @description: 简单测试
*/
public class UserService {
@Autowired
private UserRepository userRepository;
@Test
public void addUser(){
// 设置user对象,没头发没对象的苦逼码农
User u = new User();
u.setUserName("layman");
u.setAge(25);
// 地中海发型
u.setHairNumber(25);
u.setHasGirlFriend(false);
userRepository.save(u);
}
}
Spring JPA 的查询方法
基本查询
spring data 默认实现,只要继承JpaRepository,就可以使用
自定义查询
根据方法名来自动生成SQL,主要的语法是 findXXBy, readAXXBy, queryXXBy, countXXBy, getXXBy后面跟上实体类中的属性名称,
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
* @author layman
* @description: UserRepository
* @date 2021/10/29
*/
public interface UserRepository extends JpaRepository<User, Long> {
User findByUserName(String userName);
List<User> findByUserNameLike(String userName);
User findByUserNameIgnoreCase(String userName);
}
复杂查询
在实际开发中,会用到 分页、删选、连表 等查询,此时需要特殊的方法或者自定义 SQL。
以分页查询为例,分页查询非常普遍。
spring data jpa已经实现了分页功能,在查询方法中,需要传入参数Pageable,建议Pageable做为最后一个传入参数。
Pageable是 spring 封装的分页实现类,使用的时候需要传入页数、每页条数和排序规则。
简单演示
UserRepository.java
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
* @author layman
* @description: UserRepository
* @date 2021/10/29
*/
public interface UserRepository extends JpaRepository<User, Long> {
// 复杂查询(分页查询)
Page<User> findALL(Pageable pageable);
Page<User> findByUserName(String userName,Pageable pageable);
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.PostMapping;
/**
* @author layman
* @description: 简单测试
*/
public class UserService {
@Autowired
private UserRepository userRepository;
@PostMapping("/list")
public Page<User> testPageQuery(String userName, Integer pageNo, Integer pageSize) {
/**
* pageNo: 页数
* pageSize: 每页条数
* Sort.Direction.DESC: 降序
* id: 根据id进行降序排列
*/
Pageable pageable = new PageRequest(pageNo - 1, pageSize, Sort.Direction.DESC, "id");
Page<User> userList = userRepository.findByUserName(userName, pageable);
return userList;
}
}
自定义查询
Spring data 大部分 SQL都可以根据方法名定义的方式来实现,但是由于某些原因,我们想使用自定义的 SQL 来查询,spring data 也是可以支持的,如下所示:
package com.banana.springcloud.test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* @author layman
* @description: UserRepository
* @date 2021/10/29
*/
public interface UserRepository extends JpaRepository<User, Long> {
/**
* SQL操作的是实体类的属性,最后会映射到数据库表的字段上
* ?1 :对应方法入参的第一个参数
* ?2 :对应方法入参的第二个参数
*/
@Modifying
@Query("update User u set u.userName = ?1 where u.id = ?2")
int modifyByIdAndUserId(String userName, Long id);
@Transactional(timeout = 10)
@Query("select u from User u where u.userName = ?1")
User findByEmailAddress(String userName);
}