( 七 )、 SpringBoot 整合 JPA

 ( 七 )、 SpringBoot 整合 JPA

 

 

 

JPA官网: https://docs.spring.io/spring-data/jpa/docs/2.3.1.RELEASE/reference/html/#jpa.repositories

 1、maven 依赖:

      <!--spring-data-jpa-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

 

2、yml配置:

spring:
  application:
    name: jpa-study
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      # mysql 8.0 以下
      # url: jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=utf8&autoReconnect=true
      # driver-class-name: com.mysql.jdbc.Driver
      # mysql 8.0 以上使用
      url: jdbc:mysql://localhost:3306/my-study?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: 123456
  jpa:
    hibernate:
#      自动更新ddl-auto是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。该参数的几种配置如下:
      #    ·create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
      #    ·create-drop:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
      #    ·update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,
# 即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。 # ·validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。 ddl
-auto: update # 开启驼峰_转换 naming: physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy # 日志中显示sql语 show-sql: true

 

3、示例:

新建实体Person类:

@Entity
@Data
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name", length = 20)
    private String name;

    @Column(name = "age", length = 4)
    private int age;
}

@Entity 注解代表它是数据库持久化类

@GeneratedValue作用:

 @GeneratedValue注解存在的意义主要就是为一个实体生成一个唯一标识的主键、@GeneratedValue提供了主键的生成策略。

strategy属性:提供四种值:

AUTO  主键由程序控制, 是默认选项 ,不设置就是这个
IDENTITY 主键由数据库生成, 采用数据库自增长, Oracle不支持这种方式
SEQUENCE 通过数据库的序列产生主键, MYSQL  不支持
Table 提供特定的数据库产生主键, 该方式更有利于数据库的移植

Dao:

@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
    
}

 

controller:

@RestController
public class PersonController {

    @Autowired
    private PersonRepository personRepository;

    /**
     * 新增
     * @param person
     */
    @PostMapping(path = "addPerson")
    public void addPerson(Person person) {
        personRepository.save(person);
    }

    /**
     * 删除
     * @param userId
     */
    @DeleteMapping(path = "deletePerson")
    public void deletePerson(Long userId) {
        //删除名字是张三的记录
        Person person = new Person();
        person.setName("张三");
        personRepository.delete(person);

        //删除主键是userId的记录(李四)
        personRepository.deleteById(userId);
    }

    /**
     * 查询
     * @return
     */
    @GetMapping("getPerson/{userId}")
    public Object getPerson(@PathVariable("userId") Long userId){
        Optional<Person> byId = personRepository.findById(userId);
//        Person person = byId.get();
        return byId;
    }

    /**
     * 修改 save(String var1)
     * 添加和更新(修改)操作都是save(T t)方法,逻辑是根据主键判断的,如果数据库中有数据能匹配到参数中的主键,就更新匹配到的数据,否则就新添加。
     * @return
     */
    @PutMapping("update")
    public Object updatePerson(){
        Person person = new Person();
        person.setId(2L);
        person.setName("张三丰");
        return personRepository.save(person);
    }
}

 

 

4、自定义查询--根据方法名创建查询

方法名称中受支持的关键字

关键词示例JPQL片段

And

findByLastnameAndFirstname

… where x.lastname = ?1 and x.firstname = ?2

Or

findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

Is, Equals

findByFirstnamefindByFirstnameIsfindByFirstnameEquals

… 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, Null

findByAge(Is)Null

… 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(参数附后%

EndingWith

findByFirstnameEndingWith

… where x.firstname like ?1(带前缀的参数%

Containing

findByFirstnameContaining

… where x.firstname like ?1(参数绑定在中%

OrderBy

findByAgeOrderByLastnameDesc

… where x.age = ?1 order by x.lastname desc

Not

findByLastnameNot

… where x.lastname <> ?1

In

findByAgeIn(Collection<Age> ages)

… where x.age in ?1

NotIn

findByAgeNotIn(Collection<Age> ages)

… 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)

 

5、使用  @Query

使用@Query注解在接口方法之上自定义执行SQL。

eg:

public interface UserRepository extends JpaRepository<User, Long> {

  @Query("select u from User u where u.emailAddress = ?1 AND u.userName=?2")
  User findByEmailAddress(String emailAddress, String userName);
}

like @Query中的高级表达式

public interface UserRepository extends JpaRepository<User, Long> {

  @Query("select u from User u where u.firstname like %?1")
  List<User> findByFirstnameEndsWith(String firstname);
}

本地查询

该 @Query注释允许通过设定运行的原生查询 nativeQuery 标志设置为true,如图以下示例:

nativeQuery 属性如果设置为 true 时,表示的意思是:可以执行原生sql语句,所谓原生sql,也就是说这段sql拷贝到数据库中,然后把参数值给一下就能运行了,原生的 sql 都是真实的字段,真实的表名。如果设置为 false 时,就不是原生的 sql ,而不是数据库对应的真正的表名,而是对应的实体名,并且 sql 中的字段名也不是数据库中真正的字段名,而是实体的字段名;

public interface UserRepository extends JpaRepository<User, Long> {

  @Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
  User findByEmailAddress(String emailAddress);
}

通过使用以下方法在查询方法中声明本机计数查询以进行分页 @Query

public interface UserRepository extends JpaRepository<User, Long> {

  @Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
    countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
    nativeQuery = true)
  Page<User> findByLastname(String lastname, Pageable pageable);
}

使用排序

排序可以通过提供PageRequestSort直接使用来完成

 

修改查询

@Modifying,如以下示例所示:

 

删除查询

 

复杂的条件查询以及分页、排序

* 分页、排序、复杂条件查询
* 在涉及到复杂的查询的时候,尤其是多条件查询的时候,如果通过命名方式实现,长长的方法名将是代码显得十分的不优雅。
* 这个时候,大多数人会选择使用NativeQuery,通过编写SQL语句来实现,这种方式导致的结果就是项目代码中遍地是SQL,
* 随着时间的推移,项目已经失去了使用JPA的初衷。能否有一种方式,在保障JPQL的风格里完成这种复杂的查询呢?
* 这里介绍一种简单的方式:JpaSpecificationExecutor

 

Dao层继承 JpaSpecificationExecutor<T>该接口允许基于JPA标准的API规范的运行。

@Repository
public interface PersonRepository extends JpaRepository<Person, Long>, JpaSpecificationExecutor<Person> {
    @Query("select p from Person AS p where  p.age < ?1")
    List<Person> findByAgeLessThan(Integer age, Pageable pageable);

}

 

Service:

1: 简单的查询条件连接:

 @Override
    public Page<Person> findByPageAndParams(Person personParams, Integer startPage, Integer pageSize) {
        // 排序信息
        ///Sort.Direction是个枚举有ASC(升序)和DESC(降序),eg:按年龄age字段升序排序
        Sort ageSort = Sort.by(Sort.Direction.ASC, "age");
        //分页信息 注意JPA的page是从0开始算的,page传0,查询的是第1页的数据
        Pageable pageable = PageRequest.of(0, 10, ageSort);
        //查询条件构造
        Specification<Person> specification = new Specification<Person>() {
            @Override
            public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                /**
                 * @param *root: 代表查询的实体类.
                 * @param query: 可以从中得到 Root 对象, 即告知 JPA Criteria 查询要查询哪一个实体类. 还可以
                 * 来添加查询条件, 还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象.
                 * @param *cb: CriteriaBuilder 对象. 用于创建 Criteria 相关对象的工厂. 当然可以从中获取到 Predicate 对象
                 * @return: *Predicate 类型, 代表一个查询条件.
                 */
                Path<String> name = root.get("name");
                Path<Integer> age = root.get("age");
                Predicate like = criteriaBuilder.like(name, "%" + personParams.getName() + "%");
                Predicate lt = criteriaBuilder.lt(age, personParams.getAge());
                // 多个条件拼接起来
                Predicate predicate = criteriaBuilder.and(like, lt);
                return predicate;
            }
        };
        Page<Person> personPageList = personRepository.findAll(specification, pageable);
        return personPageList;
    }

 

这里需要用到CriteriaBuilder提供的几个方法: 将多个条件(predicate)连接

 Predicate and(Predicate... restrictions);
 Predicate or(Predicate... restrictions);

 

CriteriaQuery实现多条件组合

  public Page<Person> findByPageAndParams(Person personParams, Integer startPage, Integer pageSize) {
        // 排序信息
        ///Sort.Direction是个枚举有ASC(升序)和DESC(降序),eg:按年龄age字段升序排序
        Sort ageSort = Sort.by(Sort.Direction.ASC, "age");
        //分页信息 注意JPA的page是从0开始算的,page传0,查询的是第1页的数据
        Pageable pageable = PageRequest.of(0, 10, ageSort);
        //查询条件构造
        Specification<Person> specification = new Specification<Person>() {
            @Override
            public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                /**
                 * @param *root: 代表查询的实体类.
                 * @param query: 可以从中得到 Root 对象, 即告知 JPA Criteria 查询要查询哪一个实体类. 还可以
                 * 来添加查询条件, 还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象.
                 * @param *cb: CriteriaBuilder 对象. 用于创建 Criteria 相关对象的工厂. 当然可以从中获取到 Predicate 对象
                 * @return: *Predicate 类型, 代表一个查询条件.
                 */
                Path<Integer> id = root.get("id");
                Path<String> name = root.get("name");
                Path<Integer> age = root.get("age");
                Predicate like = criteriaBuilder.like(name, "%" + personParams.getName() + "%");
                Predicate lt = criteriaBuilder.lt(age, personParams.getAge());
                // 多个条件拼接起来
                Predicate predicate = criteriaBuilder.and(like, lt);
                //添加where条件
                criteriaQuery.where(predicate);
                // //指定查询项,select后面的东西
                criteriaQuery.multiselect(name,age,criteriaBuilder.count(id));
                //分组
                criteriaQuery.groupBy(id);
                //排序
                criteriaQuery.orderBy(criteriaBuilder.asc(id));
                //筛选
                criteriaQuery.having(criteriaBuilder.greaterThan(id,0));
                //获取最终的Predicate
                Predicate restriction = criteriaQuery.getRestriction();
                return restriction;
            }
        };
        Page<Person> personPageList = personRepository.findAll(specification, pageable);
        return personPageList;
    }

 

CriteriaQuery与entityManager整合

@PersistenceContext
    private EntityManager entityManager;

    public List<Person> test(){
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        //User指定了查询结果返回至自定义对象
        CriteriaQuery<Person> query = cb.createQuery(Person.class);
        Root<Person> root = query.from(Person.class);
        Path id = root.get("id");
        List<Predicate> predicates=new ArrayList<Predicate>();
        Predicate predicateId = cb.equal(id,1);
        predicates.add(predicateId);
        Path<Person> email = root.get("email");
        Predicate predicateEmail = cb.equal(email, "aa@qq.com");
        predicates.add(predicateEmail);
        Predicate endPredicate = cb.and(predicates.toArray(new Predicate[predicates.size()]));
        //添加where条件
        query.where(endPredicate);
        //指定查询项,select后面的东西
//        query.multiselect(id,email);
        //分组
        query.groupBy(id);
        //排序
        query.orderBy(cb.asc(id));
        //筛选
        query.having(cb.greaterThan(id,0));
        TypedQuery<Person> q = entityManager.createQuery(query);
        List<Person> result = q.getResultList();
        for (Person person : result) {
            //打印查询结果
            System.out.println(person.toString());
        }
        return result;
    }

 

动态sql构建

public Page<Person> test2(Person personParams, Integer startPage, Integer pageSize) {
        // 排序信息
        ///Sort.Direction是个枚举有ASC(升序)和DESC(降序),eg:按年龄age字段升序排序
        Sort ageSort = Sort.by(Sort.Direction.ASC, "age");
        //分页信息 注意JPA的page是从0开始算的,page传0,查询的是第1页的数据
        Pageable pageable = PageRequest.of(0, 10, ageSort);
        //查询条件构造
        Specification<Person> specification = new Specification<Person>() {
            @Override
            public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                /**
                 * @param *root: 代表查询的实体类.
                 * @param query: 可以从中得到 Root 对象, 即告知 JPA Criteria 查询要查询哪一个实体类. 还可以
                 * 来添加查询条件, 还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象.
                 * @param *cb: CriteriaBuilder 对象. 用于创建 Criteria 相关对象的工厂. 当然可以从中获取到 Predicate 对象
                 * @return: *Predicate 类型, 代表一个查询条件.
                 */
                Path<String> name = root.get("name");
                Path<Integer> age = root.get("age");
                Predicate conjunction = criteriaBuilder.conjunction();
                List<Expression<Boolean>> expressions = conjunction.getExpressions();
                if(!StringUtils.isEmpty(name)){
                    expressions.add(
                            criteriaBuilder.and(criteriaBuilder.like(name, "%" + personParams.getName() + "%"))
                    );   
                }
                if (age != null) {
                    expressions.add(
                            criteriaBuilder.and(criteriaBuilder.lt(age, personParams.getAge()))
                    );
                }

                return conjunction;
            }
        };
        Page<Person> personPageList = personRepository.findAll(specification, pageable);
        return personPageList;
    }

 

posted @ 2020-06-19 13:24  邓维-java  阅读(531)  评论(0编辑  收藏  举报