JPA
mybatisPlus
mybatis
JPA
Spring Data JPA为Java Persistence API(JPA)提供了实现。它简化了通过JPA访问数据库的开发工作,提供了很多CRUD的快捷操作,还提供了如分页、排序、复杂查询、自定义查询(JPQL)等功能,Spring Data JPA底层也是依赖于Hibernate来实现的,Spring Data JPA拥有标准化、简单易用、面向对象等优势,并且Spring将EntityManager 的创建与销毁、事务管理等代码抽取出来,并由Spring统一进行管理。
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
application.yml配置
server:
#指定服务端口号
port: 8008
spring:
application:
#应用名称
name: spring-data-jpa
datasource:
#你的数据库密码
password: 123456
#你的数据库地址
url: jdbc:mysql://localhost:3306/spring_test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
#数据库用户名
username: root
#数据库驱动名称
driver-class-name: com.mysql.cj.jdbc.Driver #配置MySQL的驱动程序类
#指定连接池类型
type: com.zaxxer.hikari.HikariDataSource
#数据库连接池的配置
hikari:
#客户端等待连接池连接的最大毫秒数
connection-timeout: 30000
#连接池中维护的最小空闲连接数
minimum-idle: 4
#最大池大小
maximum-pool-size: 8
#允许连接池在连接池中空闲的最长时间(毫秒)
idle-timeout: 30000
#池中连接关闭的最长生命周期(毫秒)
max-lifetime: 45000
#从池返回的连接的默认自动提交行为(默认为true)
auto-commit: true
#连接池的名称
pool-name: SpringDataJPAHikariCP
#jpa相关配置
jpa:
hibernate:
#DDL:用于定义数据库的三层结构,包括外模式、概念模式、内模式及其相互之间的映像,定义数据的完整性,安全控制等约束
ddl-auto: none #什么也不做
#其他可选值
#create: 每次运行应用程序时,都会重新创建表,所以,数据都会丢失
#create-drop:每次运行程序时会创建表结构,然后程序结束时清空数据
#update: 每次运行程序没有表时会创建表,如果对象改变会更新表结构,原有数据不会清除,只会更新
#validate: 运行程序会校验数据与数据库的字段类型是否相同,字段不同会报错
#打印执行的sql及参数
show-sql: true
# 关闭懒加载配置,否则会报错
open-in-view: false
properties:
hibernate:
#输出sql语句
show_sql: true
#格式化输出的sql,否则会一行显示
format_sql: true
环境准备:
数据库脚本
这里简单介绍一下表和表之间的关系,一个Banner对应多个BannerItem,属于一对多关系,一个Goods商品可以属于多个商品分类,一个商品分类下面会有多个商品,属于多对多关系,需要用第三张表goods_category_relation来进行组织。
JPA使用:
1.生成Entity实体类
2.创建repository包,编写Repository接口,需要继承JpaRepository接口,指定两个泛型
3.在Repository接口中编写对应方法3.1 简单查询
3.2 排序
3.3 分页
3.4 复杂查询- 自定义JPQL
3.5 JPA原生查询
3.6 多表联结之一对多关系映射
3.7 多表联结之多对多关系映射
3.8 动态sql
1.生成Entity实体类
@Table(name = “ding_xzbg_admin”)
@Entity:标志为一个JPA的实体类
@Getter、@Setter:表示设置实体类属性的Getter、Setter方法。
@Id注解:标志数据库表的主键字段。
@GeneratedValue(strategy = GenerationType.IDENTITY):指定主键的生成策略,当前指定的策略为主键自增,由数据库进行生成。
@Column:实体和数据表列不同名的时候才有用,如数据库字段banner_id,实体类字段id,这时就要使用@Column注解来标志数据库字段名称@Column(name=“banner_id”)
@Basic:标志是数据库表的列,这个注解会默认加载字段上,如果数据表没有这个字段将会报错。
@Transient:如果想忽略实体类的某一个字段时,可以使用这个注解,JPA就不会将这个字段作为数据表的列。
@Entity
@Getter
@Setter
public class Banner {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date modifyTime;
private Integer deleted;
private Integer enable;
private Long creator;
}
2.创建repository包,编写Repository接口,需要继承JpaRepository接口,指定两个泛型
第一个泛型是实体类的类型,第二个泛型是主键的类型。
public interface BannerRepository extends JpaRepository<Banner, Long> {
}
//另一种定义repository的方式
//通过@RepositoryDefinition注解来指定实体类的类型和主键的类型
@RepositoryDefinition(domainClass = Banner.class, idClass = Long.class)
public interface BannerRepositoryDefinition {
}
3.在Repository接口中编写对应方法
3.1 简单查询
通过bannerId 查询banner
public interface BannerRepository extends JpaRepository<Banner, Long> {
/**
* 简单查询-- 通过bannerId 查询banner
*
* @param bannerId bannerId
* @return banner 详细信息
*/
List<Banner> findBannerById(Long bannerId);
}
//测试类
@Test
public void testFindBannerById() {
//通过bannerId 查询banner
List<Banner> bannerInfo = bannerRepository.findBannerById(1L);
log.info(bannerInfo);
}
3.2 排序
在查询方法的入参中加入Sort对象作为入参,就可以实现排序功能
//排序
List<Goods> findAll(Sort sort);
//测试类
@Test
public void testSortFindAll() {
//使用Sort.by静态方法来快速构建Sort对象
//先按照价格降序排列,再按照id升序排列
Sort sort=Sort.by(Sort.Order.desc("price"), (Sort.Order.asc("id")) );
List<Goods> list=goodsRepository.findAll(sort);
log.info("{}", JSON.toJSONString( list ) );
}
3.3 分页
在查询方法的入参中加入分页参数Pageable
/**
* JPA实现分页
*/
Page<Goods> findAll(Pageable pageable);
//测试
@Test
public void testPageableFindAll() {
//起始页,每页大小,排序对象
Pageable pageable = PageRequest.of(0, 1,Sort.by(Sort.Order.asc("price")));
Page<Goods> goodsPage = goodsRepository.findAll(pageable);
log.info("{}", JSON.toJSONString(goodsPage));
log.info("总页数:{}", goodsPage.getTotalPages());
log.info("记录总数:{}", goodsPage.getTotalElements());
log.info("当前页索引(第几页):{}", goodsPage.getNumber());
log.info("查询的结果信息:{}", goodsPage.getContent());
log.info("当前页上的元素数量:{}", goodsPage.getNumberOfElements());
log.info("请求页的Pageable:{}", goodsPage.getPageable());
log.info("页大小:{}", goodsPage.getSize());
log.info("页的排序参数:{}", goodsPage.getSort());
log.info("页面上是否有内容:{}", goodsPage.hasContent());
log.info("是否有下一页:{}", goodsPage.hasNext());
log.info("是否有上一页:{}", goodsPage.hasPrevious());
log.info("当前页是否是最后一个:{}", goodsPage.isLast());
log.info("下一页的 Pageable:{}", goodsPage.nextPageable());
log.info("下一页的 Pageable:{}", goodsPage.previousPageable());
}
3.4 复杂查询- 自定义JPQL(无参/有参)
如果JPA规范定义的查询关键字不能满足需求的话,就可以使用@Query自定义查询的JPQL
自定义查询:
/**
* 查询id值最大的商品信息--无参
*/
@Query(value = "SELECT g from Goods g WHERE id = (SELECT max(id) FROM Goods)", nativeQuery = false)
Goods getMaxIdGoods();
/**
* 有参--方式一
*/
@Query("SELECT g FROM Goods g WHERE name=?1 and price >= ?2")
List<Goods> findGoodsByFirstParam(String name, BigDecimal price);
/**
* 有参--方式二
*/
@Query("SELECT g FROM Goods g WHERE name= :name and price >= :price")
List<Goods> findGoodsBySecondParam(@Param("name") String name, @Param("price") BigDecimal price);
只查询某几个字段
如果我们想仅仅查询出某几个字段,可以自定义一个仅包含指定字段的构造函数,如我们只想查询商品名称和价格,那么就可以定义只包含两个字段的构造函数。
public Goods(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
编写如下的JPQL查询,使用new的方式在JPQL查询中构造实体类,这是JPQL的特性
@Query("SELECT new Goods (g.name, g.price) FROM Goods g")
List<Goods> getGoodsNameAndPriceInfo();
自定义修改更新:
@Modifying注解来标志是一个更新操作
使用@Transactional注解来标志当前方法不是只读事务
@Query("UPDATE Goods g SET g.price = :price WHERE id = :goodsId")
@Modifying
@Transactional(readOnly = false)
int updateGoodsPrice(@Param("goodsId") Long goodsId,
@Param("price") BigDecimal price);
3.5 JPA原生查询
自定义查询都是使用JPQL来进行查询,下面使用原生SQL来进行查询,nativeQuery用来标志当前查询是一条原生SQL
(想要拿到原生查询的部分结果,就使用Map来进行接收)
@Query(value = "SELECT * FROM goods", nativeQuery = true)
List<Goods> findAllNativeQuery();
//如果我们想要拿到原生查询的部分结果,就使用Map来进行接收
@Query(value = "SELECT name, price FROM goods", nativeQuery = true)
List<Map<String, Object>> getGoodsNameAndPriceNativeQuery();
3.6 多表联结之一对多关系映射
数据库表关系:
一个Banner对应多个BannerItem,属于一对多关系
一个Goods商品可以属于多个商品分类,属于一对多关系
在Banner实体类中添加BannerItem的映射关系
@OneToMany注解来指定一对多关系
fetch用来指定当前查询是懒加载还是急加载,这里设置为急加载
@JoinColumn注解指定的是要关联的“多”的那张表的实体类属性,这里要注意是实体类属性而不是数据库字段
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "bannerId")
private List<BannerItem> bannerItemList;
@Test
public void testOneToManyQuery() {
List<Banner> allBannerInfo = bannerRepository.findAll();
log.info("{}", JSON.toJSONString(allBannerInfo));
}
3.7 多表联结之多对多关系映射
数据库表关系:
一个Goods商品可以属于多个商品分类,属于多对多关系
一个商品分类下面会有多个商品,属于多对多关系
@ManyToMany进行多对多配置,使用急加载的方式
@JoinTable注解来指定多对多的第三张表,name属性指定数据表的名称,joinColumn属性指定的是当前实体在第三张表中的关联字段名称
inverseJoinColumns属性指定的是要关联的另一张表的关联字段的名称
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "goods_category_relation", joinColumns = @JoinColumn(name = "category_id"),
inverseJoinColumns = @JoinColumn(name = "goods_id"))
private List<Goods> goodsList;
3.8 动态sql
方法一:用criteria查询
方法二:直接使用原生SQL,构建query来执行
方式一:用criteria查询
//关键代码
Specification<Report> specification=new Specification<Report>() {
public Predicate toPredicate(Root<Report> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
//动态sql拼接
if (StringUtil.isNotEmpty(vo.getName())) {
predicates.add(criteriaBuilder.like(root.get("name"), "%" + vo.getName() + "%"));
}
criteriaBuilder.and(
criteriaBuilder.like(root.get("name"), "%" + vo.getName() + "%")
);
return query.where(predicates.toArray(new Predicate[predicates.size()]));
}
};
//查询
reportRepository.findAll( specification,pageable);
方法二:直接使用原生SQL,构建query来执行
EntityManager直接创建原生查询
createNativeQuery的参数(string sql,info.class)
//在impl实现类引入
private LocalContainerEntityManagerFactoryBean entityManagerFactory;
//具体的方法
public List<ReportVo> getFlowReportList(ReportVo vo,Pageable pageable) {
EntityManager em = entityManagerFactory.getNativeEntityManagerFactory().createEntityManager();
StringBuilder sb=new StringBuilder(300);
sb.append("SELECT * FROM public.entity where 1=1");
Map<String,Object> map = new HashMap<>();
//拼接动态参数
//起草人
if( StringUtil.isNotEmpty(vo.getName()) ){
sb.append(" and name like :name");
map.put("name",vo.getName()+"%");
}
String sql=sb.toString();
em.getTransaction().begin();
//创建原生查询的时候,将info.class类即第二个参数,写成要传回的bean,这样就可以直接用List<Bean>接收
Query query = em.createNativeQuery(sql,ReportVo.class);
List<ReportVo> obj=query.getResultList();
}