JPA基础用法:JPA在springboot中的使用_子书少卿的博客-优快云博客
目录
问题引出
初使用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的范畴,在这里及暂不整理了;