目录
●什么是Spring Data JPA?
笔者最近在自学Springboot,对比以前的SSH框架构建的项目,确实很切身体会其好处。今天先和大家分享下Spring Data JPA。
要问什么是Spring Data JPA,我们得从最原始的地方开始说起。
大家都知道,数据库是保存数据的地方,我们的项目如果需要操作数据库中的数据,最初的做法是怎样的呢?我们以MySQL为例,相信大家在学校最开始接触的都是JDBC的方式去访问数据库。我们随便找一个网上里例子来看看,固定套路的步骤,大量的模板代码。
private static void deal() {
// 1.配置数据源
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/csdn_db";
String username = "root";
String password = "csdn";
Connection conn = null;
try {
// 2.注册JDBC驱动
Class.forName(driver);
// 3.建立数据库连接
conn = (Connection) DriverManager.getConnection(url, username, password);
// 4.建立SQL语句对象
String sql = "insert into students (Name,Sex,Age) values(?,?,?)";
PreparedStatement pstmt;
try {
pstmt = (PreparedStatement) conn.prepareStatement(sql);
pstmt.setString(1, student.getName());
pstmt.setString(2, student.getSex());
pstmt.setString(3, student.getAge());
// 5.执行SQL命令
i = pstmt.executeUpdate();
// 6.处理结果集,业务逻辑(此例省略)
// 7.关闭结果集和SQL语句对象
pstmt.close();
// 8.关闭数据库连接
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
对于JDBC,当然可以通过配置文件写JDBC驱动、数据源;通过线程池的方式避免频繁建立关闭连接等来优化,但其繁琐重复的操作步骤以及数据和对象的割裂依然让人诟病。因此,大家会见到第二个东西——ORM框架。
ORM,英文Object Relational Mapping,对象关系映射。映射什么呢?把数据库中一条一条的数据/记录和项目中一个一个的对象建立对应关系,这就是映射,因此可以像操作对象一样,操作数据库中一条一条的数据了。并且,一些ORM框架还会解决数据源配置、连接池、提供常用SQL等问题。最著名的自然就是Hibernate和Mybatis。
Hibernate和Mybatis最大的区别在于,前者以方法的形式提供了自动化的SQL操作,减少了程序员编写常用SQL的工作量,当然也支持自定义SQL,只不过相比后者来说,自定义要麻烦一些,属于面向对象的ORM框架;后者则需要程序员去自己编写每一条SQL语句,一方面可以说更灵活了,另一方面也会增加一点的工作量,属于面向关系的ORM框架。当然了,二者对于关系数据的映射写法也不太一样。具体两者的区别,网上已经有很多文章在介绍了。
随着Spring社区越做越大,它也提供了自己的ORM框架,即Spring Data JPA。至此,我们知道了,原来Spring Data JPA本质上是和Hibernate、Mybatis类似的ORM框架。那么,为什么还要学习和使用它呢?
在这里,我们就需要知道Hibernate和Mybatis还有一个重要的不同就在于,前者是遵从JPA规范的,或者前者是JPA规范的一种实现;而后者则没有遵从JPA规范。什么是JPA规范呢?它其实是定义了一系列java持久化数据的接口,规定了一些对关系数据映射成(持久化成)对象的标准,例如对于表单,我们用注解@Table标志,这就是其中的一条标准。举个例子,JPA规范就像校规校纪,hibernate则是一个三好学生小明,它遵守校规校纪,并且它是一个具体的真实的学生。
既然Spring Data JPA和Hibernate都遵守JPA规范,都是JPA的一种具体实现,那会用Hibernate自然能很顺利的过渡到Spring Data JPA。至于为什么要用Spring Data JPA而不是Hibernate,笔者认为最主要的原因是它提供了JpaRepository接口,这个“神奇”的接口能够更加智能地产生SQL,不用向Hibernate那样需要去学HQL了,下一节我们结合代码来具体看看。
●“神奇”接口JpaRepository
我们采用MySQL数据库作为演示。建立数据库springboot_demo,建立表单user_info,内容如下:
CREATE TABLE `user_info` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_account` varchar(255) DEFAULT NULL COMMENT '用户账号',
`user_name` varchar(255) DEFAULT NULL COMMENT '用户姓名',
`user_password` varchar(255) DEFAULT NULL COMMENT '用户密码',
`user_age` int(255) DEFAULT NULL COMMENT '用户年龄',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
我们在项目中准备一个实体类UserInfo,映射表单user_info:
@Entity
@Table(name = "user_info")
public class UserInfo implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", unique = true, nullable = false)
private Long id;
@Column(name = "user_account")
private String userAccount;
@Column(name = "user_name")
private String userName;
@Column(name = "user_password")
private String userPassword;
@Column(name = "user_age")
private Integer userAge;
//节省篇幅,不写具体的构造函数了,根据需要自定义有参、无参的构造函数
//节省篇幅,不写具体的get、set方法了
}
可以看到,因为都是遵从JPA规范的,所以其注解的方式和Hibernate也是一样的,使用上没有困难。接下来我们需要准备dao层,这个时候就该“神奇”接口JpaRepository出场了!
public interface UserRepository extends JpaRepository<UserInfo, Long> {}
没错!这就是最最最简单的一个dao层,只要自定义一个接口继承JpaRepository就可以了,而且不需要实现类。直接就可以使用了!!
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Autowired
UserRepository userRepository;
@Test
public void JpaTest(){
UserInfo userInfo1 = new UserInfo("user1","Tommy","123",15);
UserInfo userInfo2 = new UserInfo("user2","Alex","123", 17);
userRepository.save(userInfo1);
userRepository.save(userInfo2);
System.out.printf("表单中一共有%d条数据\n",userRepository.count());
userRepository.deleteById(6L);
System.out.printf("删除用户\n");
System.out.printf("表单中一共有%d条数据\n",userRepository.count());
List<UserInfo> allUserInfos = userRepository.findAll();
Optional<UserInfo> optionalUserInfo = userRepository.findById(1L);
}
}
对的!你没看错!根本没有实现类,继承JpaRepository就已经拥有了最基本的CRUD方法。
还不够“神奇”?想拥有更多的CRUD方法怎么办,比如想查询年龄段在某个区间的用户,这时候可以自定义SQL吗?当然。但是先别急,我们让JpaRepository再给大家变个魔法。在刚才的接口里我们写两个方法:
public interface UserRepository extends JpaRepository<UserInfo, Long> {
// 查询用户年龄段在min到max间的用户
List<UserInfo> findByUserAgeBetween(int min, int max);
// 删除名字类似str,并且年龄小于age的用户
@Transactional
int deleteByUserNameLikeAndUserAgeBefore(String str, int age);
}
OK,直接用……对的你没看错,依旧不用写dao层的接口实现!注意,注解@Transactional表示这个方法采用事务机制。一般涉及到修改的建议使用事务。咱单元测试来验证下:
@Test
public void JpaTest2(){
List<UserInfo> userInfos = userRepository.findByUserAgeBetween(15,17);
System.out.printf("年龄在15-17间的用户有%d个,分别为:\n",userInfos.size());
for(UserInfo userInfo : userInfos){
System.out.printf("%s\n",userInfo.getUserName());
}
String like = "A%";
int result = userRepository.deleteByUserNameLikeAndUserAgeBefore(like , 20);
}
运行这个测试方法,观察前后数据库中数据:
●如何变出这般魔法?
变出这般魔法的前提是其遵从了JPA的实现,因此,对于这种简单查询(是的,还有更复杂的查询),我们可以遵守命名规则去写我们自己的dao层接口,并且不用去实现,不仅可以增删改查,基本上SQL体系中的关键词也都可以使用,例如:Like、 IgnoreCase、 OrderBy等。具体的关键字和用法,引用文章http://www.ityouknow.com/springboot/2016/08/20/spring-boo-jpa.html中的描述,如下:
Keyword | Sample | JPQL snippet |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age ⇐ ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
TRUE | findByActiveTrue() | … where x.active = true |
FALSE | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
因此,我们可以通过标准的命名去写一些简单查询,而不用自己实现。换句话说,JpaRepository可极大地简化为了实现各种持久层的数据库访问而写的样板代码量。
从IDEA中我们还可以看到JpaRepository的继承关系如下图:
当然了,如果这些CRUD操作依旧不能完全满足业务需求,我们自然是可以用传统方法去自定义SQL的,即实现dao层接口。
●自定义SQL
比如我们想自己实现一条SQL语句。来修改一下代码:
public interface UserRepository extends JpaRepository<UserInfo, Long> {
// 查询用户年龄段在min到max间的用户
List<UserInfo> findByUserAgeBetween(int min, int max);
// 删除名字类似str,并且年龄小于age的用户
@Transactional
int deleteByUserNameLikeAndUserAgeBefore(String str, int age);
// 自定义SQL
@Transactional
@Modifying
@Query("delete from UserInfo where userName like ?1")
int deleteByMyRule(String str);
}
与刚才的区别在于,我们新加了两个注解,一个是@Modifying,表示这是一个删除或修改的操作,如果是查询则不需要写这个注解;另一个是@Query,里面写我们自定义的JPQL语句,其中的表单对应我们已经映射的java实体类,字段对应实体类的成员变量。利用?加数字的方式表示这是第几个参数。具体JPQL的相关知识大家可以在网上查询一下,这方面资料也不少。
之所以要用JPQL语句也是为了遵守JPA规范,这样在切换底层数据源的时候,不需要再去重新写具体的SQL语句。
我们准备以下数据:
执行单元测试:
@Test
public void JpaTest4(){
String like = "%xx%";
System.out.printf("删除%d条数据",userRepository.deleteByMyRule(like));
}
数据变成如下所示:
●小结
至此,Springboot Data JPA的基本使用就已经介绍完了。核心的Repository已经给大家展示了,这也是笔者认为从Hibernate转而使用Springboot Data JPA最主要的原因。今后如果真要使用Springboot Data JPA进行开发,大家也可以参考官方文档https://docs.spring.io/spring-data/jpa/docs/current/reference/html/。今天,你学会了吗?