Spring Data JPA入门

[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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容