JPA动态sql的使用姿势

JPA基础用法:JPA在springboot中的使用_子书少卿的博客-CSDN博客

目录

问题引出

姿势一、Query by Example

姿势二、Specification

姿势三、entityManager

        1、Query

        2、拼接sql 

        更新和新增

姿势四、QueryDSL

总结


问题引出

        初使用jpa时,一直使用jpql(原生sql)和规定方法名的实行来实现业务,在特定的场景下需要用到动态查询条件时,也是直接使用jpsql的形式来声明动态条件,通过数据库的动态条件来实现;如下实例:

    @Query(value = " SELECT *  FROM water.testjpa " +
            "WHERE name = ?1 " +
            "and if(?2 != '', age = ?2, 1=1)", nativeQuery = true)
    List<JPAEntity> test(String name, String age);

        通过动态传入不同的参数值,来通过sql来实现动态条件的控制,sql打印如下

Hibernate:  SELECT *  FROM water.testjpa WHERE name = ? and if(? != '', age = ?, 1=1)

         如果数据表中的数据量不大的情况下,sql执行无压力,但如果数据量达到百万级,这种通过条件判断形式的性能要比直接sql拼接具体条件性能差很多;

        那么如果实现动态生成sql成了新的问题;

姿势一、Query by Example

        一种通过操作entity实例来控制动态条件的实现方式,操作简单但缺陷也很明显,往下看↓

        使用限制:(缺陷)

                1、只支持查询

                     不支持嵌套或分组的属性约束,如 age = ?1 or (age = ?1 and name= ?2)

                     只支持字符串的start/contains/ends/regex匹配和其他属性类型的精确匹配

                     不支持 :>、<、in、between等

        使用:

        1、repository接口-追加继承接口 QueryByExampleExecutor ;

@Repository
public interface TestQueryByExampleRepository extends
        JpaRepository<JPAEntity, Long>
        // 泛型为操作的试题类型
        ,QueryByExampleExecutor<JPAEntity> {

}

        接口原码及功能如下:

public interface QueryByExampleExecutor<T> {
    // 根据条件查询一条数据
    <S extends T> Optional<S> findOne(Example<S> var1);
    // 根据条件查询结果集
    <S extends T> Iterable<S> findAll(Example<S> var1);
    // 根据条件查询结果集,并排序
    <S extends T> Iterable<S> findAll(Example<S> var1, Sort var2);
    // 根据条件查询结果集,并分页
    <S extends T> Page<S> findAll(Example<S> var1, Pageable var2);
    // 计数
    <S extends T> long count(Example<S> var1);
    // 判断值是否存在
    <S extends T> boolean exists(Example<S> var1);
}

        2、service

@Slf4j
@Service
@AllArgsConstructor(onConstructor_ = {@Autowired})
@Transactional
public class QueryByExampleService {

    /**
     * 测试方法
     */
    public void test(){
        // 通过实体对象来实现查询条件的控制
        JPAEntity jpaEntity = new JPAEntity();
        // 根据自己的查询条件给实体赋值;
        jpaEntity.setName("小黑");
        jpaEntity.setTtt(LocalDate.now());

        // 使用实体对象构建Example对象
        Example<JPAEntity> of = Example.of(jpaEntity);
        List<JPAEntity> all = testQueryByExampleRepository.findAll(of);
        System.out.println(all);
    }


    TestQueryByExampleRepository testQueryByExampleRepository;
}

        3、test

@SpringBootTest(classes = SpringbootJpaApplication.class)
@RunWith(SpringRunner.class)
public class QueryByExampleServiceTest {

    @Resource
    QueryByExampleService queryByExampleService;

    @Test
    public void test() {
        queryByExampleService.test();
    }
}

        4、直接结果sql

SELECT
    jpaentity0_.id AS id1_0_,
    jpaentity0_.address AS address2_0_,
    jpaentity0_.age AS age3_0_,
    jpaentity0_.NAME AS name4_0_,
    jpaentity0_.phone AS phone5_0_,
    jpaentity0_.ttt AS ttt6_0_ 
FROM
    testjpa jpaentity0_ 
WHERE
    jpaentity0_.ttt =? 
    AND jpaentity0_.NAME =?

        可以看到,sql中只有在构建实体是赋值的name和ttt条件

        5、这种方式还支持匹配器,来对条件进行设置,改造下service,添加匹配器和匹配规则

@Slf4j
@Service
@AllArgsConstructor(onConstructor_ = {@Autowired})
@Transactional
public class QueryByExampleService {

    /**
     * 测试方法
     */
    public void test(){
        // 通过实体对象来实现查询条件的控制
        JPAEntity jpaEntity = new JPAEntity();
        // 根据自己的查询条件给实体赋值;
        jpaEntity.setName("小黑");
        jpaEntity.setTtt(LocalDate.now());
        jpaEntity.setAddress("jing");

        // 构造一个匹配器,通过匹配器对条件进行设置
        ExampleMatcher exampleMatcher = ExampleMatcher.matching()
                // 设置忽略的条件(属性)---值为JPAEntity中的属性
                .withIgnorePaths("ttt")
                // 设置忽略大小写,如果不声明值,则对所有条件进行忽略大小写
//                .withIgnoreCase("address")
                // string类型的匹配,值使用String匹配器,可以通过枚举选择,这里使用的结尾匹配  注意:这个匹配是对查询所有条件的匹配
//                .withStringMatcher(ExampleMatcher.StringMatcher.ENDING)
                // 针对单个条件进行限制,两个值为:<对哪个条件>,<限制-一个函数式接口>
                // address 字段,结尾匹配 ‘jing’
//                .withMatcher("address",s->s.endsWith())
                // 或者通过ExampleMatcher的静态方法,
                // 注:当使用withMatcher对address进行设置时,上边的.withIgnoreCase("address")会失效,需要单独设置
                .withMatcher("address", ExampleMatcher.GenericPropertyMatchers.endsWith().ignoreCase())
                ;

        // 使用实体对象构建Example对象
        Example<JPAEntity> of = Example.of(jpaEntity,exampleMatcher);
        List<JPAEntity> all = testQueryByExampleRepository.findAll(of);
        System.out.println(all);
    }


    TestQueryByExampleRepository testQueryByExampleRepository;
}

        6、使用同一个test执行,直接结果如下

Hibernate : SELECT
jpaentity0_.id AS id1_0_,
jpaentity0_.address AS address2_0_,
jpaentity0_.age AS age3_0_,
jpaentity0_.NAME AS name4_0_,
jpaentity0_.phone AS phone5_0_,
jpaentity0_.ttt AS ttt6_0_ 
FROM
    testjpa jpaentity0_ 
WHERE
    (
    lower( jpaentity0_.address ) LIKE ?) 
    AND jpaentity0_.NAME =?

    

        如果只是字段的精确匹配或简单的匹配可以这种方式;但如果要实现更加复杂的条件,继续往下看;

姿势二、Specification

        一种通过spring data jpa提供的实现Specification的形式来自定义查询规则的方式,实现相对复杂,但可以满足一般的使用场景;但也不完美,往下看↓

        使用限制:(缺陷)

                1、不支持分组和聚合函数;(如需要则只能通过玩entityManager了,既姿势三)

                2、不能查询单列字段数据,通过接口原码也能看到,实现的方法没有办法指定select的类型导致不能查询单例;可以通过姿势三中的Query来实现单列查询;

        使用:

        1、repository接口-追加继承接口 JpaSpecificationExecutor ;

@Repository
public interface TestSpecificationRepository extends
        JpaRepository<JPAEntity, Long>,
        // 泛型为操作的试题类型
        JpaSpecificationExecutor<JPAEntity> {

}

        接口原码及功能如下:功能和QueryByExampleExecutor大体一致;

public interface JpaSpecificationExecutor<T> {
    
    Optional<T> findOne(@Nullable Specification<T> var1);

    List<T> findAll(@Nullable Specification<T> var1);

    Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);

    List<T> findAll(@Nullable Specification<T> var1, Sort var2);

    long count(@Nullable Specification<T> var1);
}

        2、service 

        条件设置可以自己根据需在criteriaBuilder中寻找

@Slf4j
@Service
@AllArgsConstructor(onConstructor_ = {@Autowired})
@Transactional
public class SpecificationService {


    public void test(){

        // 模拟入参
        JPAEntity jpaEntity = new JPAEntity();
        jpaEntity.setId(2);
        jpaEntity.setName("小黑");
        LocalDate startTime = LocalDate.of(2023,4,17);
        LocalDate endTime = LocalDate.of(2023,4,19);

        //构建一个分页实例,用于分页
        PageRequest pageRequest = PageRequest.of(0, 1);
        // findAll方法的参数需要一个Specification接口,可以直接通过匿名类的形式来实现
        Page<JPAEntity> all = testSpecificationRepository.findAll(new Specification<JPAEntity>() {

            // root :用来获取列
            // criteriaBuilder : 设置条件,如:> < in between 等
            // criteriaQuery: 组合 where  order by 等
            @Override
            public Predicate toPredicate(Root<JPAEntity> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                // 获取列的时候要将结果枚举参数声明为字段对应的类型
                // 获取列
                Path<Integer> id = root.get("id");
                Path<String> name = root.get("name");
                Path<LocalDate> ttt = root.get("ttt");

                // 创建一个容器,存放要查询的条件
                List<Predicate> list = new ArrayList<>();

                // 如果id不为空
                if (!ObjectUtils.isEmpty(jpaEntity.getId())) {
                    // id不为空,查询id 小于 传入值
//                    list.add(criteriaBuilder.le(id, jpaEntity.getId()));
                    // id不为空,查询id 大于 传入值
                    list.add(criteriaBuilder.ge(id, jpaEntity.getId()));
                }
                // 如果name不为空
                if (!StringUtils.isEmpty(jpaEntity.getName())) {
                    // 如果name不为空  查询name 等于 传入值
                    list.add(criteriaBuilder.equal(name, jpaEntity.getName()));
                }
                // 如果起始时间不为空
                if (!ObjectUtils.isEmpty(startTime) && !ObjectUtils.isEmpty(endTime)) {
                    // 如果起始时间不为空  查询ttt 介于两个值之间的值
                    list.add(criteriaBuilder.between(ttt, startTime, endTime));
                }
                // 汇总查询条件
                Predicate and = criteriaBuilder.and(list.toArray(new Predicate[0]));
                // 创建一个降序的排序,升序使用asc
                // 降序可以按多个条件,使用List<Order>即可,参照构造方法
                Order order = criteriaBuilder.desc(id);
                // 将查询条件和排序添加组合返回
                return criteriaQuery.where(and).orderBy(order).getRestriction();
            }
            // 设置分页实例
        }, pageRequest);

        // 结果中获取分页数据
        List<JPAEntity> content = all.getContent();
        System.out.println(content);
        // 获取总条数 all中有分页中所有参数,根据需要获取
        long totalElements = all.getTotalElements();
        System.out.println(totalElements);
    }

    // 注入
    TestSpecificationRepository testSpecificationRepository;
}

        3、test

@SpringBootTest(classes = SpringbootJpaApplication.class)
@RunWith(SpringRunner.class)
public class SpecificationServiceTest {

    @Resource
    SpecificationService specificationService;

    @Test
    public void test() {
        specificationService.test();
    }
}

        结果sql如下:

第一条sql

Hibernate : SELECT
jpaentity0_.id AS id1_0_,
jpaentity0_.address AS address2_0_,
jpaentity0_.age AS age3_0_,
jpaentity0_.NAME AS name4_0_,
jpaentity0_.phone AS phone5_0_,
jpaentity0_.ttt AS ttt6_0_ 
FROM
    testjpa jpaentity0_ 
WHERE
    jpaentity0_.id >= 2 
    AND jpaentity0_.NAME =? 
    AND (
        jpaentity0_.ttt BETWEEN ? 
    AND ?) 
ORDER BY
    jpaentity0_.id DESC 
    LIMIT ?

第二条sql
Hibernate : SELECT
count( jpaentity0_.id ) AS col_0_0_ 
FROM
    testjpa jpaentity0_ 
WHERE
    jpaentity0_.id >= 2 
    AND jpaentity0_.NAME =? 
    AND (
    jpaentity0_.ttt BETWEEN ? 
    AND ?)

        注意:如果使用分页查询,如果返回的数据条数不小于分页的size则会再次执行count原条件查询总数,用于组装findAll的返回Page对象;

        如果基于性能考虑只需要根据分页条件查询结果,不需要Page对象中的参数,则这种方式是不适合的;继续往下看↓

姿势三、entityManager

        看到entityManager基本已经到了jpa操作的终极地步了,可以通过自己拼接sql字符串来交给框架执行;

        首先注入一个独立的entityManager

// 注意,不要使用@Autowired的形式进行注入EntityManager,
    // 因为EntityManager存在线程安装问题,jpa没有做处理
    // 使用@PersistenceContext解决线程安全问题
    @PersistenceContext
    EntityManager em;

        1、Query

                通过entityManager获取CriteriaBuilder、CriteriaQuery、Root三个对象的形式来拼接条件,起始和姿势二中toPredicate()中的三个参数一致;

                使用起来有些复杂,但基本所有形式的查询形式都包含;

                与姿势二Specification的差异:

                        1、支持单列或者自定义返回列

                        2、支持聚合分组等;

                使用:场景一(常用)

                1、service

@Slf4j
@Service
@Transactional
public class EntityManagerService {


    public void test(){

        // 模拟入参
        String nameIn = "小黑";
        Integer idIn = 1;

        // 从entityManager中获取操作实例,后去后操作和Specification一致
        // root :用来获取列
        // criteriaBuilder : 设置条件,如:> < in between 等
        // criteriaQuery: 组合 where  order by 等

        CriteriaBuilder cb = em.getCriteriaBuilder();
        // 可以根据自己的场景选择返回哪种类型的query;也就是要返回哪种类型的数据
        // 此处使用返回Object的类型
//        CriteriaQuery<Object> query = cb.createQuery();
        // 此处使用返回JPAEntity类型
        CriteriaQuery<JPAEntity> query = cb.createQuery(JPAEntity.class);
        // from是操作的哪个实体,既哪个表
        Root<JPAEntity> root = query.from(JPAEntity.class);

        // 1、获取列
        Path<String> name = root.get("name");
        Path<Integer> id = root.get("id");

        // 2、拼接查询条件和返回字段,返回字段不声明则返回全字段
        query
                // 查询单个参数  如果返回单列可是使用具体类型的query,也可以使用实体接收,会调用实体的构造方法
//                .select(id)
                // 查询多个参数  使用实体接收,会调用实体的构造方法,如果没有固定返回的构造会报错
                .multiselect(id,name)

                // 查询条件及排序,条件和排序格则可以是多个
                .where(cb.equal(name, nameIn),cb.ge(id, idIn)).orderBy(cb.desc(id));
        // 查询结果只有1条数据,如果返回多条会报错
//        Integer singleResult = (Integer) em.createQuery(query).getSingleResult();
        List<JPAEntity> resultList = em.createQuery(query).getResultList();
        System.out.println(resultList);

    }

    // 注意,不要使用@Autowired的形式进行注入EntityManager,
    // 因为EntityManager存在线程安装问题,jpa没有做处理
    // 使用@PersistenceContext解决线程安全问题
    @PersistenceContext
    EntityManager em;
}

                2、entity添加固定字段构造,用于接收返回固定字段的查询

@Data
@Entity
@Table(
        name = "testjpa"
        // catalog="数据库名称" 是个坑,记得写对或者改的时候不要忘记,再或者不写
//        , catalog = "water"
)
@NoArgsConstructor
@AllArgsConstructor
public class JPAEntity {
    @Id// @Id 主键 添加一个空的id标识,因为jpa在映射实体是需要一个id,这个必须
//    /**
//     * TABLE:使用一个特定的数据库表格来保存主键。
//     * SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
//     * IDENTITY:主键由数据库自动生成(主要是自动增长型)
//     * AUTO:主键由程序控制。
//     */
//    mysql中的自增,不要使用AUTO

//    如果选择相同的主键插入,会被覆盖
//    @GeneratedValue(strategy = GenerationType.IDENTITY)
//    @Column(columnDefinition="int(5)")
//    @Column(columnDefinition="varchar(128) not null")
    private Integer id;
    @Column(name = "name", nullable = true, length = 20)
    private String name;
    private String age;
    private String address;
    private String phone;
    private LocalDate ttt;

    public JPAEntity(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}

                3、执行test

@SpringBootTest(classes = SpringbootJpaApplication.class)
@RunWith(SpringRunner.class)
public class EntityManagerServiceTest {

    @Resource
    EntityManagerService entityManagerService;

    @Test
    public void test() {
        entityManagerService.test();
    }
}

                4、执行结果sql

Hibernate : SELECT
jpaentity0_.id AS col_0_0_,
jpaentity0_.NAME AS col_1_0_ 
FROM
    testjpa jpaentity0_ 
WHERE
    jpaentity0_.NAME =? 
    AND jpaentity0_.id >= 1 
ORDER BY
    jpaentity0_.id DESC

             

                使用:场景二(count、case...when)

                service

@Slf4j
@Service
@Transactional
public class EntityManagerService2 {


    public void test(){

        CriteriaBuilder cb = em.getCriteriaBuilder();
        // 如果查询的结果没有接收的实体时,可以使用cb.createTupleQuery();返回Tuple集合或实例,可以当做一个数组
        CriteriaQuery<Tuple> query = cb.createTupleQuery();
        Root<JPAEntity> root = query.from(JPAEntity.class);

        query.multiselect(
                // 去重统计
                cb.countDistinct(root.get("id")),
                // case .. when 子句
                // 当name不为空时 取name,否则取id
                cb.countDistinct(
                        cb.selectCase()
                        .when(root.get("name").isNotNull(), root.get("name"))
                                .otherwise(root.get("id"))
                )
        );


        Tuple singleResult = em.createQuery(query).getSingleResult();
        System.out.println(singleResult.get(0));
        System.out.println(singleResult.get(1));

    }


    @PersistenceContext
    EntityManager em;
}

             注:count函数中不能使用其他函数;

                2、执行结果sql

Hibernate : SELECT
count( DISTINCT jpaentity0_.id ) AS col_0_0_,
count( DISTINCT CASE WHEN jpaentity0_.NAME IS NOT NULL THEN jpaentity0_.NAME ELSE jpaentity0_.id END ) AS col_1_0_ 
FROM
    testjpa jpaentity0_

                使用:场景三(子查询)

                1、service

@Slf4j
@Service
@Transactional
public class EntityManagerService3 {


    public void test(){

        CriteriaBuilder cb = em.getCriteriaBuilder();
        // 如果查询的结果没有接收的实体时,可以使用cb.createTupleQuery();返回Tuple集合或实例,可以当做一个数组
        CriteriaQuery<JPAEntity> query = cb.createQuery(JPAEntity.class);
        Root<JPAEntity> root = query.from(JPAEntity.class);

        query.multiselect(root.get("name"),root.get("address"));

        // 子查询
        Subquery<OrderEntity> subquery = query.subquery(OrderEntity.class);
        Root<OrderEntity> subRoot = subquery.from(OrderEntity.class);
        // 模糊查询
        subquery.select(subRoot.get("name")).where(cb.like(subRoot.get("name"), "小黑" + "%"));

        // 与子查询组装查询条件
//        query.where(cb.in(root.get("name")).value(subquery));
        query.where(cb.equal(root.get("name"),subquery));
        List<JPAEntity> resultList = em.createQuery(query).getResultList();
        System.out.println(resultList);


    }


    @PersistenceContext
    EntityManager em;
}

                2、entity添加指定字段构造

@Data
@Entity
@Table(
        name = "testjpa"
        // catalog="数据库名称" 是个坑,记得写对或者改的时候不要忘记,再或者不写
//        , catalog = "water"
)
@NoArgsConstructor
@AllArgsConstructor
public class JPAEntity {
    @Id// @Id 主键 添加一个空的id标识,因为jpa在映射实体是需要一个id,这个必须
//    /**
//     * TABLE:使用一个特定的数据库表格来保存主键。
//     * SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
//     * IDENTITY:主键由数据库自动生成(主要是自动增长型)
//     * AUTO:主键由程序控制。
//     */
//    mysql中的自增,不要使用AUTO

//    如果选择相同的主键插入,会被覆盖
//    @GeneratedValue(strategy = GenerationType.IDENTITY)
//    @Column(columnDefinition="int(5)")
//    @Column(columnDefinition="varchar(128) not null")
    private Integer id;
    @Column(name = "name", nullable = true, length = 20)
    private String name;
    private String age;
    private String address;
    private String phone;
    private LocalDate ttt;

    public JPAEntity(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public JPAEntity(String name, String address) {
        this.name = name;
        this.address = address;
    }
}

                3、执行结果sql

Hibernate : SELECT
jpaentity0_.NAME AS col_0_0_,
jpaentity0_.address AS col_1_0_ 
FROM
    testjpa jpaentity0_ 
WHERE
    jpaentity0_.NAME =(
    SELECT
        orderentit1_.NAME 
    FROM
        name_order orderentit1_ 
WHERE
    orderentit1_.NAME LIKE ?)

                使用:场景四(分页)

                1、service

@Slf4j
@Service
@Transactional
public class EntityManagerService4 {


    public void test(){

        CriteriaBuilder cb = em.getCriteriaBuilder();
        // 如果查询的结果没有接收的实体时,可以使用cb.createTupleQuery();返回Tuple集合或实例,可以当做一个数组
        CriteriaQuery<String> query = cb.createQuery(String.class);
        Root<JPAEntity> root = query.from(JPAEntity.class);

        // 条件:age 大于 16
        Predicate predicate = cb.greaterThan(root.get("id"), 7);
        // 查询name列
        query.select(root.get("name")).where(predicate);

        // 设置分页条件
        Pageable pageable = PageRequest.of(0, 2);
        TypedQuery<String> typedQuery = em.createQuery(query).setFirstResult((int) pageable.getOffset()).setMaxResults(pageable.getPageSize());

        // 这里就很有意思了,在姿势二中有说到如果使用接口提供的分页方法,在特定情况框架会给count总条数,但PageableExecutionUtils中提供的方法参数total是一个函数接口,需要自己实现total的计算
        // 方法返回的page对象参照姿势二;
        // 当然如果只需要按照分页条件查询,不需要分页数据,可以直接typedQuery.getResultList()获取结果集;
        Page<String> page = PageableExecutionUtils.getPage(typedQuery.getResultList(), pageable, () -> {
            // 统计数量
            CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
            Root<JPAEntity> countRoot = countQuery.from(JPAEntity.class);
            countQuery.select(cb.count(countRoot.get("id"))).where(cb.greaterThan(countRoot.get("id"), 7));
            return em.createQuery(countQuery).getSingleResult();
        });
//        System.out.println(typedQuery.getResultList());


        System.out.println(page.getContent());
        System.out.println(page.getTotalElements());



    }


    @PersistenceContext
    EntityManager em;
}

                2、执行结果sql:

第一条:查询

Hibernate : SELECT
jpaentity0_.NAME AS col_0_0_ 
FROM
    testjpa jpaentity0_ 
WHERE
    jpaentity0_.id > 7 
    LIMIT ?

第二条:count

Hibernate : SELECT
count( jpaentity0_.id ) AS col_0_0_ 
FROM
    testjpa jpaentity0_ 
WHERE
    jpaentity0_.id >7

                注:可以根据自己需要选择是否需要count

                使用:场景五(聚合)

                1、service

@Slf4j
@Service
@Transactional
public class EntityManagerService5 {


    public void test(){

        CriteriaBuilder cb = em.getCriteriaBuilder();
        // 如果查询的结果没有接收的实体时,可以使用cb.createTupleQuery();返回Tuple集合或实例,可以当做一个数组
        CriteriaQuery<Double> query = cb.createQuery(Double.class);
        Root<JPAEntity> root = query.from(JPAEntity.class);

        // 分组
//        query.multiselect(root.get("name"),cb.count(root.get("id"))).groupBy(root.get("name"));
        // 聚合:平均值
        query.select(cb.avg(root.get("age")));

        Double result = em.createQuery(query).getSingleResult();
        System.out.println(result);

    }


    @PersistenceContext
    EntityManager em;
}

                2、执行结果sql

Hibernate : SELECT
avg( jpaentity0_.age ) AS col_0_0_ 
FROM
    testjpa jpaentity0_

        2、拼接sql 

                究极方案,字符串拼接sql,交给框架执行,相对来着这种形式更加原始;

                使用 :  场景一(自定义sql)

                1、service

@Slf4j
@Service
@Transactional
public class EntityManagerService6 {


    public void test(){

        // sql字符串,这里就可以任意定义sql了
        String sql = "SELECT * FROM testjpa";
//        // jpql字符串,
//        String jpql = "FROM JPAEntity ";


//        // 使用jpql
//        List resultList = em.createQuery(jpql).getResultList();
        // 泛型为映射实体
        List resultList = em.createNativeQuery(sql, JPAEntity.class).getResultList();
        System.out.println(resultList);

    }


    @PersistenceContext
    EntityManager em;
}

                2、执行结果sql

Hibernate : SELECT
jpaentity0_.id AS id1_1_,
jpaentity0_.address AS address2_1_,
jpaentity0_.age AS age3_1_,
jpaentity0_.NAME AS name4_1_,
jpaentity0_.phone AS phone5_1_,
jpaentity0_.ttt AS ttt6_1_ 
FROM
    testjpa jpaentity0_

        更新和新增

                上边所有场景都是围绕查询的动态sql,那么如何组装动态条件的更新呢;

                方式

                        1、可以使用jpa接口提供的save(无id为新增,有id为更新);

                        2、使用entityManager的persist(新增);

                        3、使用entityManager的自定义sql;

                新增案例:

                entityManager的persist;

@Slf4j
@Service
@Transactional
public class EntityManagerService7 {


    public void test(){

        JPAEntity jpaEntity = new JPAEntity();
        jpaEntity.setId(3);
        jpaEntity.setName("小橙02");
        jpaEntity.setAddress("上海");

        // persist方法类似于Hibernate save方法,但是存在一个不同点:
        // 在数据库主键自增情况下:若提前设置id属性值,JPA EntityManager 的 persist方法将抛出异常,而Hibernate Session的 save 方法可以执行,将忽略提前设置的id值
        // 泛型为映射实体
        em.persist(jpaEntity);
        // flush ():同步持久上下文环境,即将持久上下文环境的所有未保存实体的状态信息保存到数据库中。
        em.flush();
        // clear ():清除持久上下文环境,断开所有关联的实体。如果这时还有未提交的更新则会被撤消。
        em.clear();
    }


    @PersistenceContext
    EntityManager em;
}

                       entityManager的自定义sql

@Slf4j
@Service
@Transactional
public class EntityManagerService8 {


    public void test(){

//        String insertSql = " insert into testjpa (address, age, name, id) values ('shangjingcheng',18,'小蓝',6)";
        String updateSql = " update testjpa set phone = '456' WHERE name = '地方小黑' ";

//        int i = em.createNativeQuery(insertSql).executeUpdate();
        int i = em.createNativeQuery(updateSql).executeUpdate();
        System.out.println(i);
    }

    @PersistenceContext
    EntityManager em;
}

                常见问题:有些场景下使用自定sql的更新操作不提交事务,需要配置注释使用,如下:

@Slf4j
@Service
@Transactional
public class EntityManagerService8 {


    @Transactional
    @Modifying
    @Query
    public void test(){

//        String insertSql = " insert into testjpa (address, age, name, id) values ('shangjingcheng',18,'小蓝',6)";
        String updateSql = " update testjpa set phone = '456' WHERE name = '地方小黑' ";

//        int i = em.createNativeQuery(insertSql).executeUpdate();
        javax.persistence.Query nativeQuery = em.createNativeQuery(updateSql);
        int i = nativeQuery.executeUpdate();
        System.out.println(i);
    }

    @PersistenceContext
    EntityManager em;
}

                如果通过以上方式还是无法提交事务,可以试下手动控制事务,如下:

@Slf4j
@Service
@Transactional
public class EntityManagerService8 {

    // 注入EntityManagerFactory
    @Resource
    EntityManagerFactory factory;

    public void test(){

        EntityManager em;
        EntityTransaction transaction;
        em = factory.createEntityManager();
        transaction = em.getTransaction();
        // 开启事务
        transaction.begin();

        String updateSql = " update testjpa set phone = '666' WHERE name = '地方小黑' ";
        int i = em.createNativeQuery(updateSql).executeUpdate();
        System.out.println(i);

        // 提交事务
        transaction.commit();
        em.close();
        // 自己控制
        factory.close();
    }
}

姿势四、QueryDSL

        通用查询框架,暂不整理;
          

总结

        通过以上的各种方式可以看出,使用jpa来实现动态sql还是有些麻烦的,但框架的定位不同,jpa本身就不是为自定义sql或者复杂查询而生的,但可以通过其他方式可以达到结果依是最好的结果,如果已有的项目已经选择了jpa那么在面对接口无法实现的场景下,可以根据实际场景选择相对好的方式去实现,大体可以这样选择:

        1、Query by Example:简单的字段相等判断,或者字符串的简单匹配;

        2、Specification:如果有>、<、 between 等条件,但有没有聚合等操作时使用;

        3、以上形式都不能满足时使用;

QueryDSL在这里没有整理,是通过三方的形式实现,感觉已经脱离了jpa的范畴,在这里及暂不整理了;

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值