一、spring data jpa 框架
- spring data 提供了基于 spring 的使用习惯(方式)来操作各种类型的数据,主要包括各种数据库及 no sql。因此它的适用面比 mybatis 大很多。
- 使用数据访问技术、关系和非关系数据库(no sql)、mapreduce 框架和基于云的数据库变得容易。这是一个伞形项目,其中包含许多特定与给定数据库的子项目。
- spring data 有很多子项目,其中 commons 和 jdbc 是基础,其他的针对应用的包括 jpa,mongodb,redis,elasticsearch 等。
二、jpa
- jpa 是 java persistence api 也就是 java 持久化应用接口。javaEE 5.0 中出现的操作数据库的标准,它提供了多个接口,比如 @Table、@Id、@Column 等注解都属于该标准的内容。这些内容所在的包在 javax.persistence 中。在 spring boot 项目中如果引入了 spring data jpa 的启动器,该包就会被引用。
- jpa 作为一种标准,被很多 orm 的框架所遵循,典型的是 hibernate 框架,另一个就是 spring data jpa, spring 是基于 java 语言的框架,所以它的 orm 框架遵循 jpa 标准是理所当然的。mybatis 并没有实现 jpa 标准。这两个框架各有千秋,mybatis 更优秀的在于书写复杂的查询:spring data jpa 更优秀在于通过方法名来生成不同的 sql。
三、spring data jpa 在 spring boot 中的使用
- 启动器 spring-boot-starter-data-jpa,基于启动器 spring,jdbc,spring orm,jpa 等相关的依赖都会被引入使用。
- 配置文件中,可以添加 spring.jpa 的相关配置内容:jpa:database:mysql。因为需要根据特定数据库生成 sql。
- 创建 pojo 类,需要添加 jpa 的 Entity,Table,Id 等注解。
- 建 dao 包,在包下建接口。
- 接口需要继承 JpaRepository 接口,该接口可以实现针对单表的几乎所有的 crud 操作,可以分页。还需要继承 JpaSpecificationExecutor 独立接口,它可以实现复杂条件的单表查询,也可以分页。
- 实现 service 类。
- 创建 controller 类,按照 api 文档书写各个请求处理方法。在控制器类中需要考虑跨域请求的问题,否则报错。解决办法:在控制器类上添加 @CrossOrigin 注解。
- 基于组合条件查询。
package com.tensquare.base.dao;
import com.tensquare.base.pojo.Label;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* @author 华韵流风
* @ClassName LabelDao
* @Date 2021/9/17 17:11
* @packageName com.tensquare.base.dao
* @Description TODO
*/
public interface LabelDao extends JpaRepository<Label,String>, JpaSpecificationExecutor<Label> {
}
/**
* 标签综合查询
*
* @param label label
* @return List<Label>
*/
public List<Label> findByLabel(Label label) {
List<Predicate> list = new ArrayList<>();
return labelDao.findAll((Specification<Label>) (root, criteriaQuery, criteriaBuilder) -> {
if (!StringUtils.isEmpty(label.getLabelname())) {
Predicate predicate = criteriaBuilder.like(root.get("labelname").as(String.class), "%" + label.getLabelname() + "%");
list.add(predicate);
}
if (!StringUtils.isEmpty(label.getState())) {
Predicate predicate = criteriaBuilder.like(root.get("state").as(String.class), "%" + label.getState() + "%");
list.add(predicate);
}
if (!StringUtils.isEmpty(label.getRecommend())) {
Predicate predicate = criteriaBuilder.like(root.get("recommend").as(String.class), "%" + label.getRecommend() + "%");
list.add(predicate);
}
if (!StringUtils.isEmpty(label.getId())) {
Predicate predicate = criteriaBuilder.like(root.get("id").as(String.class), "%" + label.getId() + "%");
list.add(predicate);
}
return criteriaBuilder.and(list.toArray(new Predicate[0]));
});
}
- 基于组合条件的分页查询:
- 分页查询只需要在方法中添加一个分页参数对象 Pageable,注意 jpa 中的页码是从 0 开始。
/**
* 标签分页
*
* @param label label
* @param page page
* @param size size
* @return Page<Label>
*/
public Page<Label> findByLabelPage(Label label, int page, int size) {
//jpa中的页码是从0开始,所以要减一。
return labelDao.findAll(getSpecification(label), PageRequest.of(page - 1, size));
}
- 自定义 Repository 方法。
- 条件首字母大写
- 列名首字母大写
- 注意顺序
- 复杂查询
- 对于多表的查询,比如多表的连接查询,另一种就是针对多表的子查询。实现方式主要是自己写 sql。
- spring data jpa 写 sql 有两种方式:
- 面向对象的方式,写法称为 JPQL。
- 原生的 sql(推荐)。
package com.tensquare.qa.dao;
import com.tensquare.qa.pojo.Problem;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
/**
* @author 华韵流风
*/
public interface ProblemDao extends JpaRepository<Problem, String>, JpaSpecificationExecutor<Problem> {
/**
* 最新问答
*
* @param labelid labelid
* @param pageable pageable
* @return List<Problem>
*/
@Query(value = "select * from tb_problem p,tb_pl pl where p.id=pl.problemid and pl.labelid=?1 order by createtime desc", nativeQuery = true)
List<Problem> findNewList(String labelid, Pageable pageable);
/**
* 热门问答
*
* @param labelid labelid
* @param pageable pageable
* @return List<Problem>
*/
@Query(value = "select * from tb_problem p,tb_pl pl where p.id=pl.problemid and pl.labelid=:labelid order by reply desc", nativeQuery = true)
List<Problem> findHotList(@Param("labelid") String labelid, Pageable pageable);
/**
* 等待问答
*
* @param labelid labelid
* @param pageable pageable
* @return List<Problem>
*/
@Query(value = "select * from tb_problem p,tb_pl pl where p.id=pl.problemid and pl.labelid=:labelid and reply=0", nativeQuery = true)
List<Problem> findWaitList(@Param("labelid") String labelid, Pageable pageable);
}
四、使用 spring data redis 操作缓存
- 创建 redis 容器:docker run -di –-name=tensquare_redis -p 6379:6379 redis –restart=always。
- 引用启动器:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 在配置文件中配置 redis 服务器:
server:
port: 9005
spring:
application:
name: tensquare-article
jpa:
database: mysql
show-sql: true
datasource:
username: root
password: 123456
url: jdbc:mysql://192.168.46.130:3306/tensquare_article?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false&useUnicode=true&allowMultiQueries=true
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 5
minimum-idle: 2
idle-timeout: 2000
redis:
host: 192.168.46.130
#port: 6379使用默认的端口可不写
- 使用:
package com.tensquare.article.service;
import com.tensquare.article.dao.ArticleDao;
import com.tensquare.article.pojo.Article;
import com.tensquare.utils.IdWorker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author 华韵流风
* @ClassName ArticleService
* @Date 2021/9/18 14:09
* @packageName com.tensquare.article.service
* @Description TODO
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class ArticleService {
@Autowired
private ArticleDao articleDao;
@Autowired
private IdWorker idWorker;
/**
* 注入redis模板,这里若使用@Autowired注解则需要使用
* 泛型的RedisTemplate。即不指定类型。
*/
@Resource
private RedisTemplate<String, Article> redisTemplate;
/**
* 添加article
*
* @param article article
*/
public void add(Article article) {
article.setId(String.valueOf(idWorker.nextId()));
articleDao.save(article);
}
/**
* 根据id删除article
*
* @param id id
*/
public void remove(String id) {
articleDao.deleteById(id);
redisTemplate.delete("article_" + id);
}
/**
* 根据id修改article
*
* @param id id
* @param article article
*/
public void update(String id, Article article) {
article.setId(id);
articleDao.save(article);
redisTemplate.delete("article_" + id);
redisTemplate.opsForValue().set("article_" + id, findById(id), 24 * 60 * 60, TimeUnit.SECONDS);
}
/**
* 根据id查询article
*
* @param id id
* @return Article
*/
public Article findById(String id) {
//从缓存中查找
Article article = redisTemplate.opsForValue().get("article_" + id);
if (article == null) {
article = articleDao.findById(id).get();
redisTemplate.opsForValue().set("article_" + id, article, 24 * 60 * 60, TimeUnit.SECONDS);
}
return article;
}
/**
* 查询所有article
*
* @return List<Article>
*/
public List<Article> findAll() {
return articleDao.findAll();
}
}