spring data jpa 入门使用笔记
- 一、JPA的一些面试常识问题:
- 二、spring boot + JPA的简单项目
- 1.导入pom依赖
- 2.实体类POJO配置@Entity、@Table、@Id、@GeneratedValue
- 2.2.实体类中特殊注解,及用法:@GeneratedValue 主键自增 , @Transient不储存在数据库,
- 2.2.1. @GeneratedValue(strategy = GenerationType.IDENTITY)
- 2.2.2. @OneToOne 一对一、 @ManyToMany 多对多、 @OneToMany 一对多、@OneToOne(cascade = CascadeType.REFRESH) // 一对一映射
- 2.2.3. CascadeType的各级权限CascadeType.REFRESH
- 2.2.4. fetch = FetchType.LAZY懒加载,FetchType.EAGER急加载
- 2.2.5. @JoinTable和@JoinColumn和joinColumns和inverseJoinColumns的用法
- 2.2.6. @OneToMany,orphanRemoval = true:对应是否联级删除
- 3.application.properties文件中添加配置
- 4.dao层继承JPA核心接口
- 5.基本操作
- 6.JPA修改局部字段
一、JPA的一些面试常识问题:
1. JPA与Hibernate关系
JPA是持久化规范,而Hibernate是其实现
2. JPA与JDBC的区别
(1)JPA开发效率高,运行效率低
(2)JDBC开发效率低,运行效率高(更接近底层,代码繁琐)
(3)JPA兼容各种数据库(方便移植)
(4)JPA有内置缓存(性能在一定程度上有所优化)
(5)JPA直接面向持久对象操作
(6)JPA不能干涉SQL的生成
二、spring boot + JPA的简单项目
1.导入pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
2.实体类POJO配置@Entity、@Table、@Id、@GeneratedValue
@Entity //指定持久化实体类
@Table(name = "t_employee") //指定表名
public class Employee {
/**
* @Id 表示主键
* @GeneratedValue 表示主键自动递增
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Integer age;
private String name;
private Integer height;
@Transient
private List<String> menu;
@OneToOne(cascade = CascadeType.REFRESH) // 一对一映射
private SysDicPO sysDic; //系统编码
}
2.2.实体类中特殊注解,及用法:@GeneratedValue 主键自增 , @Transient不储存在数据库,
2.2.1. @GeneratedValue(strategy = GenerationType.IDENTITY)
2.2.2. @OneToOne 一对一、 @ManyToMany 多对多、 @OneToMany 一对多、@OneToOne(cascade = CascadeType.REFRESH) // 一对一映射
2.2.3. CascadeType的各级权限CascadeType.REFRESH
* CascadeType.REMOVE
Cascade remove operation,级联删除操作。
删除当前实体时,与它有映射关系的实体也会跟着被删除。
* CascadeType.MERGE
Cascade merge operation,级联更新(合并)操作。
当Student中的数据改变,会相应地更新Course中的数据。
* CascadeType.DETACH
Cascade detach operation,级联脱管/游离操作。
如果你要删除一个实体,但是它有外键无法删除,你就需要这个级联权限了。它会撤销所有相关的外键关联。
* CascadeType.REFRESH
Cascade refresh operation,级联刷新操作。
假设场景 有一个订单,订单里面关联了许多商品,这个订单可以被很多人操作,那么这个时候A对此订单和关联的商品进行了修改,与此同时,B也进行了相同的操作,但是B先一步比A保存了数据,那么当A保存数据的时候,就需要先刷新订单信息及关联的商品信息后,再将订单及商品保存。(来自良心会痛的评论)
* CascadeType.ALL
Cascade all operations,清晰明确,拥有以上所有级联操作权限。
链接:https://www.jianshu.com/p/e8caafce5445
2.2.4. fetch = FetchType.LAZY懒加载,FetchType.EAGER急加载
如果是EAGER,那么表示取出这条数据时,它关联的数据也同时取出放入内存中
如果是LAZY,那么取出这条数据时,它关联的数据并不取出来,在同一个session中,什么时候要用,就什么时候取(再次访问数据库)。
但是,在session外,就不能再取了。用EAGER时,因为在内存里,所以在session外也可以取。
一般只在一边设Eager,JPA接口默认为一对多为Lazy,多对一为Eager,但是Hibernate反向工程生成Entity时,多对一为Lazy,需要手动改为Eager。
而两边都设Eager,那么代码中取一条记录时,会发2次SQL。
链接:https://blog.csdn.net/tongyang8820/article/details/71157948
2.2.5. @JoinTable和@JoinColumn和joinColumns和inverseJoinColumns的用法
JoinTable是中间表表名 joinColumns指定中间表中关联自己ID的字段,inverseJoinColumns表示中间表中关联对方ID的字段,joinColumn是列名.
@JoinTable(name = "employee_role",joinColumns = @JoinColumn(name = "employee_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
//用法2-referencedColumnName自己主表的字段,name=子表的对应字段,orphanRemoval = true:对应是否联级删除
@OneToMany(fetch = FetchType.LAZY,cascade = CascadeType.ALL,orphanRemoval = true)
@JoinColumn(name="buildingRiskId",referencedColumnName="id",insertable=true,updatable=true)
private Set<LocaleRiskPO> localeRiskPOSet;
//-------------------------------------------------------------------------------------------------
public class UserInfo{
@OneToMany(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
@JoinTable(name = "tbl_ouser_nuser",
joinColumns={@JoinColumn(name="nid",referencedColumnName="id")},
inverseJoinColumns={@JoinColumn(name="oid",referencedColumnName="id")})
private Set<OldAccountPO> oldAccountSet;
}
2.2.6. @OneToMany,orphanRemoval = true:对应是否联级删除
@OneToMany(fetch = FetchType.LAZY,cascade = CascadeType.ALL,orphanRemoval = true)
@JoinColumn(name="buildingRiskId",referencedColumnName="id",insertable=true,updatable=true)
private Set<LocaleRiskPO> localeRiskPOSet;
3.application.properties文件中添加配置
#数据源为mysql
spring.jpa.database = MYSQL
# Show or not log for each sql query
#控制台打印
spring.jpa.show-sql = true
#update表示需要实体类时会更新
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
# stripped before adding them to the entity manager)
#JPA方言
#spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
#命名策略
spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultNamingStrategy
4.dao层继承JPA核心接口
4.1.SpringBoot JPA提供的核心接口
- Repository接口
- CrudRepository接口
- PagingAndSortingRepository接口
- JpaRepository接口
- JPASpecificationExecutor接口
package com.deceen.dao;
import com.deceen.entity.DemoEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface DemoMapper extends JpaRepository<DemoEntity,Integer> {
List<DemoEntity> findByAgeAndHeight(Integer age,Integer height);
List<DemoEntity> findByNameLike(String name);
@Query("from DemoEntity where name = ?1")
List<DemoEntity> queryByNameUseHQL(String name);
@Query(value = "select * from demo_entity where name=?",nativeQuery = true)
List<DemoEntity> queryByNameUseSQL(String name);
@Query(value = "update demo_entity set name=? where id=?",nativeQuery = true)
@Modifying //需要执行一个更新操作
void updateUsersNameById(String name,Integer id);
}
5.基本操作
5.1. 注入EntityManager entityManager;可以实现自定义sql查询
@Autowired
EntityManager entityManager;
//JPA测试entityManager.createNativeQuery
//测试 query 返回实体类 entity
//测试 mapToInt 计算对象和
@Test
void test01(){
String sql="select * from demo_entity where id<:size";
Query q = entityManager.createNativeQuery(sql);
q.setParameter("size",30);
//设置成map
q.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
q.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(DemoEntity.class));
List l = q.getResultList();
System.out.println(l);
int sum = l.stream().mapToInt(m -> ((DemoEntity) m).getHeight()).sum();
System.out.println("所有高度和:"+ sum);
}
5.2. 排序查询
@Test
void find() {
// //Order 定义了排序规则
// Sort.Order order=new Sort.Order(Sort.Direction.DESC,"id");
//Sort对象封装了排序规则
// Sort sort=Sort.by(order);
Sort sort1=Sort.by(Sort.Direction.DESC,"height");
List<DemoEntity> xiao= demoMapper.findAll(sort1);
xiao.stream().forEach(System.out::println);
}
5.3. 分页查询
@Test
void find2() {
// 分页Pageable
DemoEntity entity = new DemoEntity();
entity.setAge(15);
entity.setHeight(20);
//sort条件
Example<DemoEntity> sort= Example.of(entity);
//pageable 分页包装类
Pageable pageable =PageRequest.of(0,2,Sort.Direction.DESC,"id");
Page<DemoEntity> page= demoMapper.findAll(sort,pageable);
long total = page.getTotalElements();
int totalPages = page.getTotalPages();
List<DemoEntity> xiao = page.getContent();
System.out.println("总记录数:"+total);
System.out.println("总页数:"+totalPages);
xiao.stream().forEach(System.out::println);
}
6.JPA修改局部字段
解决办法
通常有两种方法解决此问题:
1、通过传入对象的id,从数据库中查询得到原始对象,然后将要修改的字段封装到原始对象中。再以封装后的对象为参数进行save()。
代码如下:
public FrontResult update(Evaluation evaluation) {
// 从数据库中获取对象
Evaluation original = evaluationRepo().findById(evaluation.getId());
// 复制想要更改的字段值
BeanUtils.copyProperties(evaluation, original, getNullPropertyNames(evaluation));
// 更新操作
evaluationRepo().save(original);
return FrontResult.init(FrontResult.SUCCEED, "更新成功");
}
使用的工具类如下(用于获取未被修改的字段名):
public class UpdateUtil {
public static String[] getNullPropertyNames(Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<>();
for (java.beans.PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) {
emptyNames.add(pd.getName());
}
}
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
}
2、通过注解@Query自己实现sql语句。
注意:
在执行update或者delete方法时,必须加上注解@Modifying 和 @Transactional。
此处的Test要使用实体的类名,不是数据库中的表名。
代码如下:
@Modifying
@Transactional
@Query("update Test a set " +
"a.name = CASE WHEN :#{#testAre.name} IS NULL THEN a.name ELSE :#{#testAre.name} END ," +
"a.age = CASE WHEN :#{#testAre.age} IS NULL THEN a.age ELSE :#{#testAre.age} END ," +
"a.insertTime = CASE WHEN :#{#testAre.insertTime} IS NULL THEN a.insertTime ELSE :#{#testAre.insertTime} END ," +
"a.spare = CASE WHEN :#{#testAre.spare} IS NULL THEN a.spare ELSE :#{#testAre.spare} END " +
"where a.id = :#{#testAre.id}")
int update(@Param("testAre") TestAre testAre);
一般字段比较多时我会选第一种方式,虽然多查了一次数据库,但是省键盘。
https://www.cnblogs.com/hanstrovsky/p/11867878.html