好物安利:Spring Data JPA

目录

 

●什么是Spring Data JPA?

●“神奇”接口JpaRepository

●如何变出这般魔法?

●自定义SQL

●小结


●什么是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中的描述,如下:

KeywordSampleJPQL snippet
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age ⇐ ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection age)… where x.age not in ?1
TRUEfindByActiveTrue()… where x.active = true
FALSEfindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase

… 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/。今天,你学会了吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值