Spring Data JPA入门

作者: 风少侠 | 来源:发表于2019-07-20 11:47 被阅读0次

    [TOC]

    SpringData JPA是spring基于ORM框架、JPA规范的基础上封装的一套JPA应用框架,可以使开发者使用极简的代码实现对数据库的访问和操作。它提供了包括增删改查等在内的基本功能,且易于扩展。

    springdata jpa、jpa和hibernate三者关系

    通俗来讲springdata jpa是对jpa规范的一层封装,hibernate实现了jpa规范。

    java代码----->springdata jpa ------>jpa规范------>hibernate------>jdbc ----->mysql数据库

    graph LR
    A[java代码] -->B(spring data jpa)
    B --> |jpa规范| C(hibernate)
    C -->|jdbc| D(mysql数据库)
    

    我们使用java代码调用springdata jpa的api,springdata jpa封装了jpa规范,并且内部使用的是hibernate实现,hibernate封装了jdbc进行数据库操作。

    入门案例

    1、创建工程,导入依赖

        compile group: 'org.hibernate', name: 'hibernate-core', version: '5.4.3.Final'
        compile group: 'org.hibernate', name: 'hibernate-c3p0', version: '5.4.3.Final'
        compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.16'
        compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '2.1.9.RELEASE'
        testCompile group: 'org.springframework', name: 'spring-test', version: '5.1.8.RELEASE'
    

    2、编写spring配置文件

    • 配置spring相关

    • 数据源信息

    • jpa的实现方式

    • 配置要用到的实体类

    • 配置jpa实现方的配置信息

    • 配置事务管理器

    • 声明式事务

    <?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:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--spring-->
        <!--配置spring的注解扫描-->
        <context:component-scan base-package="com.lxf"/>
    
    
        <!--spring data jpa-->
        <!--整合spring data jpa-->
        <jpa:repositories base-package="com.lxf.dao" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager" />
    
        <!--创建实体管理器工厂,交给spring管理-->
        <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <!--配置数据源-->
            <property name="dataSource" ref="dataSource"/>
            <!--配置要扫描的包,实体所在包-->
            <property name="packagesToScan" value="com.lxf.entity"/>
            <!--配置jpa的实现方-->
            <property name="persistenceProvider">
                <bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
            </property>
    
            <!--jpa的实现方的配置-->
            <property name="jpaVendorAdapter">
                <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                    <!--数据库类型-->
                    <property name="database" value="MYSQL"/>
                    <!--控制台显示sql语句-->
                    <property name="showSql" value="true"/>
                    <!--是否自动创建数据库表-->
                    <property name="generateDdl" value="true"/>
                    <!--数据库方言-->
                    <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
                </bean>
            </property>
    
            <!--jpa方言:高级特性-->
    
        </bean>
    
        <!--数据源-->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/study?serverTimezone=GMT"/>
            <property name="user" value="root"/>
            <property name="password" value="crystal1024"/>
            <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        </bean>
    
        <!--配置事务管理器-->
        <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
            <property name="entityManagerFactory" ref="entityManagerFactory"/>
        </bean>
    
        <!--声明式事务-->
        
    </beans>
    

    3、创建实体类,编写实体类和数据库表关系映射

    参考JPA规范。

    4、编写dao层接口

    • 需要继承两个接口

      • JpaRepository:封装了增删改查分页排序等基本操作,具体可以看JpaRepository的父类
      graph TB
      A[Repository] -->B(CrudRepository)
      B --> C(PagingAndSortingRepository)
      C -->D(JpaRepository)
      
      • JpaSpecificationExecutor:封装了标准查询
    • 提供相应的泛型

      • JpaRepository
        • 操作的实体类型
        • 实体中主键类型
      • JpaSpecificationExecutor
        • 操作的实体类型
    public interface UserDao extends JpaRepository<User,Integer>, JpaSpecificationExecutor<User> {
    }
    
    • 会通过动态代理自动生成相应方法

    5、测试

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:spring_data_jpa.xml")
    public class Test {
        @Autowired
        private UserDao userDao;
    
        @org.junit.Test
        public void textSave(){
            User user = new User();
            user.setName("小红");
            user.setAge(22);
            User userResult = userDao.save(user);
            System.out.println(userResult);
        }
    }
    

    User{id=4, name='小红', age=22, sex=null, address='null', phone='null'}

    操作数据库

    调用spring data jpa的api

    插入/更新

    • save方法:传入的实体对象有主键则更新,没有主键则插入。
        @org.junit.Test
        public void testSave(){
            User user = new User();
            user.setName("小红");
            user.setAge(22);
            User userResult = userDao.save(user);
            System.out.println(userResult);
        }
    

    删除

    • delete系列方法
        @org.junit.Test
        public void testDelete(){
            userDao.deleteById(2);
        }
    

    查询

    • count:统计

    • exists系列方法:数据库中是否存在

    • find系列方法:立即加载

    • getOne:延迟加载,返回的是一个动态代理对象

        @org.junit.Test
        public void testFindOne(){
    //        Optional<User> user = userDao.findById(2);
    //        System.out.println(user.get());
            User user = userDao.getOne(2);
            System.out.println(user);
        }
    
        @org.junit.Test
        public void testApi(){
            long count = userDao.count();
            boolean b = userDao.existsById(2);
        }
    

    语句操作

    除了调用spring data jpa内置的api,我们也可以在dao接口中定义我们自己的方法,通过@Query声明jpql或sql语句。

    • @Query
      • value:数据库操作语句
      • nativeQuery:是否是原生查询,默认false,即默认使用jpql查询
    • @Modifying:声明当前是一个更新操作,需要修改数据库数据。
      • 只能用于void或int/Integer的返回类型
      • 因为需要修改数据库数据,未防止修改失败造成未知后果,需要搭配事务管理来是使用
    • @Transactional:添加事务管理支持
      • 一般需要设置rollbackFor或者noRollbackFor,来表示什么情况下进行事务回滚
    • @Rollback:是否可以回滚,默认true

    jpql查询

    public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
    
        @Query(value = "from User where name = :name and age = :age")
        public User findUserByName(@Param("name") String userName,@Param("age") int age);
    }
    

    jpql更新

    @Query(value = "update User set name = :name where id = :id")
    @Modifying
    public Integer updateNameById(@Param("id") int id,@Param("name") String userName);
    
    
        @org.junit.Test
        @Transactional(rollbackFor = Exception.class)
        //@Rollback(value = false)//如果设置为fasle,即使发生异常也不会回滚
        public void testJpql(){
            User user = userDao.findUserByName("lili",18);
            System.out.println(user);
    
            userDao.updateNameById(user.getId(),"lili_2");
        }
    

    原生sql语句查询

    public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
        @Query(value = "select * from user where name = :name and age = :age",nativeQuery = true)
        public User findUserByName(@Param("name") String userName,@Param("age") int age);
    }
    

    约定规则查询

    spring data jpa制定了一些约定,如果按照这些约定来定义方法名,则会自动解析出sql语句。

    findBy + 属性名 + 查询方式 + (And|Or) + 属性名 + 查询方式...

    查询方式 方法命名 sql where字句
    And findByNameAndPwd where name= ? and pwd =?
    Or findByNameOrSex where name= ? or sex=?
    Is,Equals findById,findByIdEquals where id= ?
    Between findByIdBetween where id between ? and ?
    LessThan findByIdLessThan where id < ?
    LessThanEquals findByIdLessThanEquals where id <= ?
    GreaterThan findByIdGreaterThan where id > ?
    GreaterThanEquals findByIdGreaterThanEquals where id > = ?
    After findByIdAfter where id > ?
    Before findByIdBefore where id < ?
    IsNull findByNameIsNull where name is null
    isNotNull,NotNull findByNameNotNull where name is not null
    Like findByNameLike where name like ?
    NotLike findByNameNotLike where name not like ?
    StartingWith findByNameStartingWith where name like '?%'
    EndingWith findByNameEndingWith where name like '%?'
    Containing findByNameContaining where name like '%?%'
    OrderBy findByIdOrderByXDesc where id=? order by x desc
    Not findByNameNot where name <> ?
    In findByIdIn(Collection<?> c) where id in (?)
    NotIn findByIdNotIn(Collection<?> c) where id not in (?)
    True findByAaaTue where aaa = true
    False findByAaaFalse where aaa = false
    IgnoreCase findByNameIgnoreCase where UPPER(name)=UPPER(?)

    简单挑几个示例:

    public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
        public User findByName(String name);
    
        public User findByNameLike(String name);
    
        public User findByNameLikeAndAge(String name, int age);
    
        public List<User> findByIdBetween(int idMin, int idMax);
    }
    
    
    
        @org.junit.Test
        public void testName(){
            User user1 = userDao.findByName("tom");
            System.out.println(user1);
    
            User user2 = userDao.findByNameLike("t%");
            System.out.println(user2);
    
            User user3 = userDao.findByNameLikeAndAge("tom",18);
            System.out.println(user3);
    
            List<User> users = userDao.findByIdBetween(1, 3);
            users.forEach(new Consumer<User>() {
                @Override
                public void accept(User user) {
                    System.out.println(user);
                }
            });
        }
    

    Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_, user0_.phone as phone5_0_, user0_.sex as sex6_0_ from user user0_ where user0_.name=?
    User{id=3, name='tom', age=18, sex=1, address='null', phone='null'}
    Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_, user0_.phone as phone5_0_, user0_.sex as sex6_0_ from user user0_ where user0_.name like ? escape ?
    User{id=3, name='tom', age=18, sex=1, address='null', phone='null'}
    Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_, user0_.phone as phone5_0_, user0_.sex as sex6_0_ from user user0_ where (user0_.name like ? escape ?) and user0_.age=?
    User{id=3, name='tom', age=18, sex=1, address='null', phone='null'}
    Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_, user0_.phone as phone5_0_, user0_.sex as sex6_0_ from user user0_ where user0_.id between ? and ?
    User{id=2, name='lili2', age=18, sex=1, address='null', phone='null'}
    User{id=3, name='tom', age=18, sex=1, address='null', phone='null'}

    标准查询(Specification)

    我们上面提到过,springdata jpa的dao层一般继承2个接口JpaRepository和JpaSpecificationExecutor。JpaRepository封装了crud、统计、排序、分页的常见操作,而JpaSpecificationExecutor基于JPA的criteria查询封装了另一种查询方式,我们之前一直在使用JpaRepositoru中的方法,下面来看下JpaSpecificationExecutor接口,它里面只提供了5个方法:

    public interface JpaSpecificationExecutor<T> {
        //查询一个
        Optional<T> findOne(@Nullable Specification<T> spec);
        //查询全部
        List<T> findAll(@Nullable Specification<T> spec);
        //查询全部  提供分页功能
        Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
        //查询全部,提供排序功能
        List<T> findAll(@Nullable Specification<T> spec, Sort sort);
        //统计
        long count(@Nullable Specification<T> spec);
    }
    

    可以看到,这5个方法有个共同点,接收一个Specification参数。

    Specification

    Specification是对JPA规范中Root、CriteriaQuery、CriteriaBuilder的一层封装,用于构建过滤条件。实例化Specification需要实现它的toPerdicate方法:

    //参数含义在我的另一文JPA规范中有介绍,简单说来Root用于获得查询属性,CriteriaBuilder用于构建过滤条件,CriteriaQuery用于指定最终查询语句,这里一般不会使用,默认为where语句。
    Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
    

    注意这里创建出来的是where查询语句。

    来个简单示例,查询表中年龄大于等于18的所有河南人:

        @Test
        public void test(){
            Specification<User> specification = new Specification<User>() {
                @Override
                public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                    //分别构造各个单属性的过滤条件
                    Predicate namePredicate = criteriaBuilder.like(root.get("address"), "河南%");
                    Predicate agePredicate = criteriaBuilder.ge(root.get("age"), 18);//大于等于
    
                    //组合成最终的过滤条件
                    Predicate predicate = criteriaBuilder.and(namePredicate, agePredicate);
                    return predicate;
                }
            };
            
            //查询
            List<User> users = userDao.findAll(specification);
            users.forEach(new Consumer<User>() {
                @Override
                public void accept(User user) {
                    System.out.println(user);
                }
            });
        }
    

    如果要添加排序和分页,可以使用Sort和Pageable。

    • Sort:排序
    Sort sort = new Sort(Sort.Direction.DESC,"id");//排序属性可以设置多个
    List<User> users = userDao.findAll(specification,sort);
    
    • Pageable:分页,是一个接口,可以通过PageRequest构建实例。
    Sort sort = new Sort(Sort.Direction.DESC,"id");
    //Pageable pageable = PageRequest.of(0,10);//pageIndex,pageSize
    Pageable pageable = PageRequest.of(0,10,sort);
    Page<User> users = userDao.findAll(specification, pageable);
    users.forEach(new Consumer<User>() {
       @Override
       public void accept(User user) {
          System.out.println(user);
       }
    });
    

    spring boot中的springdata jpa配置

    application.yaml

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/study?serverTimezone=GMT
        username: root
        password: crystal1024
      jpa:
        show-sql: true
        hibernate:
          ddl-auto: update
    

    Demo源码地址

    https://github.com/lunxinfeng/jpa/tree/master/springdatajpa

    相关文章

      网友评论

        本文标题:Spring Data JPA入门

        本文链接:https://www.haomeiwen.com/subject/dziylctx.html