JPA的基本使用
这段时间看了下JPA,简单的做个笔记吧。
1、搭建环境
1.1、application.yaml
我用的是Mysql,需要数据库连接驱动,数据源看你心情吧,系统也有默认的数据源。
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/examinedb?useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
database: MYSQL
show-sql: true
open-in-view: true
properties:
hibernate:
enable_lazy_load_no_trans: true #使用延时加载时控制Session的生命周期
dialect: org.hibernate.dialect.MySQL5Dialect
ddl-auto: update
thymeleaf:
cache: false
secure:
ignored:
urls:
- /swagger-resources/**
- /v2/api-docs/**
- /swagger-ui.html
- /*.html
- /**/*.html
- /**/*.css
- /**/*.js
- /**/*.png
- /**/*.jpg
server:
port: 81
1.2、构建ORM关系映射模型
@Entity
标注是JPA的实体,@Table
和数据库哪一个表做的映射。@ManyToMany(targetEntity = Role.class)
标识多对多,同理一对多,一对一也是基本这个套路。不同点在于,多对多要把中间表描述出来,把支撑三表连接的连接列表明出来。
package com.cbf.pojo;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
/**
* @Description
* @Author wangnaixing
* @Date 2021-12-25 12:27:03
*/
@Data
@Entity
@Table ( name ="t_user" , schema = "")
public class User implements Serializable {
private static final long serialVersionUID = 5450815487641580632L;
/**
* 自增主键
*/
@Id
@Column(name = "id" )
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
/**
* 用户名
*/
@Column(name = "username" )
private String username;
/**
* 密码
*/
@Column(name = "pwd" )
private String pwd;
/**
* 角色集合
*/
@ManyToMany(targetEntity = Role.class)
@JoinTable(name = "t_user_role_relation",joinColumns = {@JoinColumn(name = "user_id")},inverseJoinColumns = {@JoinColumn(name="role_id")})
private List<Role> roleList;
}
1.3、实现接口
啥都不用写了,太爽了。人家会在你启动SpringBoot项目的时候,自动的生成实现类。
package com.cbf.repo;
import com.cbf.pojo.Role;
import org.springframework.data.repository.CrudRepository;
/**
* @author by wangnaixing
* @Description
* @Date 2021/12/25 13:26
*/
public interface RoleDao extends CrudRepository<Role,Long> {
}
2、增删改查实现
在Controller层,本人直接注入的JPA的Dao.来操作CRUD,可看出增删改查都有现成的方法,用就可以了。唯独分页,需要Pageable对象,了解到你的分页信息。在查询方法中,只要带上这个参数执行查询的集合出来就会分页,你可以使用org.springframework.data.domain.Page
(SpringBoot)自带的分页结果容器接受到这些分页信息,比如当前页,总页码,分页记录,之类的这些。
1、添加
/**
* 新增学生
* @param entity
* @return
*/
@PostMapping("/save")
public void save(Student entity){
log.info("==系统正在新增学生==");
studentDao.save(entity);
log.info("==系统成功新增学生!==");
}
2、修改
/**
* 修改学生
*/
@GetMapping("/update/{id}")
public void update(Student entity){
log.info("==系统正在修改学生==");
studentDao.save(entity);
log.info("==系统成功修改学生!==");
}
3、删除
/**
* 删除学生
* @param id
*/
@GetMapping("/delete/{id}")
public void delete(@PathVariable(name = "id") Long id){
log.info("==系统正在删除学生==");
studentDao.deleteById(id);
log.info("==系统成功删除学生!==");
}
4、根据ID查询
/**
* 根据Id查询学生信息
* @param id
*/
@GetMapping("/findById/{id}")
public String findById(@PathVariable(name = "id") Long id, Model model){
log.info("==系统正在查询学生==");
Student student = studentDao.findById(id).orElse(null);
model.addAttribute("student",student);
log.info("==系统成功查询学生!==");
return "forward:/toEditStudentPage";
}
4、分页
因为本人角色,使用SpringBoot的分页信息对象直接返回前端不合理,其理由在于有太多不必要的参数了,我通常会定义一个分页交互模型,把需要的数据抽离,从Page抽离出来,再给到前端。
/**
* 分页查询
* @param pageNo
* @param pageSize
* @return
*/
@RequestMapping("/findByPage")
public String findByPage(
@RequestParam(name = "pageNo",defaultValue = "0") Integer pageNo,
@RequestParam(name = "pageSize",defaultValue = "5") Integer pageSize, Model model){
log.info("==系统查询学生==");
//分页查询
List<Student> studentList = new ArrayList<>();
Pageable pageable = PageRequest.of(pageNo, pageSize);
CommonPage<Student> page = CommonPage.restPage(studentDao.findAll(pageable));
//结果存入请求域
model.addAttribute("page",page);
//结果存入请求域
model.addAttribute("studentList",studentList);
//转发的学生list页面
log.info("==系统成功查询学生!==");
return "/student/student_list";
}
package com.cbf.repo;
import com.cbf.pojo.Student;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;
/**
* @author by wangnaixing
* @Description
* @Date 2021/12/25 10:07
*/
public interface StudentDao extends CrudRepository<Student,Long> {
/**
* 分页查询
* @param pageable
* @return
*/
Page<Student> findAll(Pageable pageable);
}
package com.cbf.common;
import org.springframework.data.domain.Page;
import java.util.List;
/**
* 分页数据封装类
* @author wangnaixing
* @param <T>
*/
@Data
public class CommonPage<T> {
private Integer pageNum;
private Integer pageSize;
private Integer totalPage;
private Long total;
private List<T> list;
/**
* 将SpringData分页后的list转为分页信息
*/
public static <T> CommonPage<T> restPage(Page<T> pageInfo) {
CommonPage<T> result = new CommonPage<T>();
result.setTotalPage(pageInfo.getTotalPages());
result.setPageNum(pageInfo.getNumber());
result.setPageSize(pageInfo.getSize());
result.setTotal(pageInfo.getTotalElements());
result.setList(pageInfo.getContent());
return result;
}
}
3、生成实体类的插件
比较头疼的事情是什么,就是ORM模型构建,这个表创建好了,一个一个创建对象,再加注解,简直是搞自己心态吧。其实呢,IDEA已经给我们提供了插件。
两步就能自动生成Model模型了。
3.1、第一步:配置
找到表,右键到序号3的编辑器中,覆盖文件内容。文件内容如下。仔细看配置,你就能明白这个脚本,做一些自己的调整,和MyBatis Genernate MyBatis Plus 那种生成针对的方面也差不多,啥包位置了,作者啦,去表前缀了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LHop7Ohz-1640484782622)(D:\my_notebook\typora笔记图片统一管理处\image-20211226100411122.png)]
import com.intellij.database.model.DasTable
import com.intellij.database.model.ObjectKind
import com.intellij.database.util.Case
import com.intellij.database.util.DasUtil
import java.text.SimpleDateFormat
/*
* Available context bindings:
* SELECTION Iterable<DasObject>
* PROJECT project
* FILES files helper
*/
packageName = ""
typeMapping = [
(~/(?i)tinyint|smallint|mediumint/) : "Integer",
(~/(?i)int/) : "Long",
(~/(?i)bool|bit/) : "Boolean",
(~/(?i)float|double|decimal|real/) : "BigDecimal",
(~/(?i)datetime|timestamp|date|time/) : "Date",
(~/(?i)blob|binary|bfile|clob|raw|image/): "InputStream",
(~/(?i)/) : "String"
]
FILES.chooseDirectoryAndSave("Choose directory", "Choose where to store generated files") { dir ->
SELECTION.filter { it instanceof DasTable && it.getKind() == ObjectKind.TABLE }.each { generate(it, dir) }
}
def generate(table, dir) {
def className = javaClassName(table.getName(), true)
def fields = calcFields(table)
packageName = getPackageName(dir)
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(new File(dir, className + ".java")), "UTF-8"))
printWriter.withPrintWriter { out -> generate(out, className, fields, table) }
// new File(dir, className + ".java").withPrintWriter { out -> generate(out, className, fields,table) }
}
// 获取包所在文件夹路径
def getPackageName(dir) {
return dir.toString().replaceAll("\\\\", ".").replaceAll("/", ".").replaceAll("^.*src(\\.main\\.java\\.)?", "") + ";"
}
def generate(out, className, fields, table) {
out.println "package $packageName"
out.println ""
out.println "import javax.persistence.Column;"
out.println "import javax.persistence.Entity;"
out.println "import javax.persistence.Table;"
out.println "import javax.persistence.Id;"
out.println "import javax.persistence.GeneratedValue;"
out.println "import java.io.Serializable;"
Set types = new HashSet()
fields.each() {
types.add(it.type)
}
if (types.contains("Date")) {
out.println "import java.util.Date;"
}
if (types.contains("BigDecimal")) {
out.println "import java.math.BigDecimal;"
}
if (types.contains("InputStream")) {
out.println "import java.io.InputStream;"
}
out.println ""
out.println "/**\n" +
" * @Description \n" +
" * @Author wangnaixing\n" + //1. 修改idea为自己名字
" * @Date " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " \n" +
" */"
out.println ""
out.println "@Entity"
out.println "@Table ( name =\"" + table.getName() + "\" , schema = \"\")" //2. schema = \"后面添加自己的表空间名称(mysql可以不添加, 不用这个schema属性也行)
out.println "public class $className implements Serializable {"
out.println ""
out.println genSerialID()
fields.each() {
out.println ""
// 输出注释
if (isNotEmpty(it.commoent)) {
out.println "\t/**"
out.println "\t * ${it.commoent.toString()}"
out.println "\t */"
}
if ((it.annos+"").indexOf("[@Id]") >= 0) out.println "\t@Id"
if (it.annos != "") out.println " ${it.annos.replace("[@Id]", "")}"
// 输出成员变量
out.println "\tprivate ${it.type} ${it.name};"
}
// 输出get/set方法
fields.each() {
out.println ""
out.println "\tpublic ${it.type} get${it.name.capitalize()}() {"
out.println "\t\treturn this.${it.name};"
out.println "\t}"
out.println ""
out.println "\tpublic void set${it.name.capitalize()}(${it.type} ${it.name}) {"
out.println "\t\tthis.${it.name} = ${it.name};"
out.println "\t}"
}
// 输出toString方法
out.println ""
out.println "\t@Override"
out.println "\tpublic String toString() {"
out.println "\t\treturn \"{\" +"
fields.each() {
out.println "\t\t\t\t\t\"${it.name}='\" + ${it.name} + '\\'' +"
}
out.println "\t\t\t\t'}';"
out.println "\t}"
out.println ""
out.println "}"
}
def calcFields(table) {
DasUtil.getColumns(table).reduce([]) { fields, col ->
def spec = Case.LOWER.apply(col.getDataType().getSpecification())
def typeStr = typeMapping.find { p, t -> p.matcher(spec).find() }.value
def comm = [
colName : col.getName(),
name : javaName(col.getName(), false),
type : typeStr,
commoent: col.getComment(),
annos : "\t@Column(name = \"" + col.getName() + "\" )"]
if ("id".equals(Case.LOWER.apply(col.getName())))
comm.annos += ["@Id"]
fields += [comm]
}
}
// 这里是处理数据库表前缀的方法,这里处理的是t_xxx命名的表
// 已经修改为使用javaName, 如果有需要可以在def className = javaName(table.getName(), true)中修改为javaClassName
// 处理类名(这里是因为我的表都是以t_命名的,所以需要处理去掉生成类名时的开头的T,
// 如果你不需要去掉表的前缀,那么请查找用到了 javaClassName这个方法的地方修改为 javaName 即可)
def javaClassName(str, capitalize) {
def s = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str)
.collect { Case.LOWER.apply(it).capitalize() }
.join("")
.replaceAll(/[^\p{javaJavaIdentifierPart}[_]]/, "_")
// 去除开头的T http://developer.51cto.com/art/200906/129168.htm
s = s[1..s.size() - 1]
capitalize || s.length() == 1 ? s : Case.LOWER.apply(s[0]) + s[1..-1]
}
def javaName(str, capitalize) {
// def s = str.split(/(?<=[^\p{IsLetter}])/).collect { Case.LOWER.apply(it).capitalize() }
// .join("").replaceAll(/[^\p{javaJavaIdentifierPart}]/, "_")
// capitalize || s.length() == 1? s : Case.LOWER.apply(s[0]) + s[1..-1]
def s = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str)
.collect { Case.LOWER.apply(it).capitalize() }
.join("")
.replaceAll(/[^\p{javaJavaIdentifierPart}[_]]/, "_")
capitalize || s.length() == 1 ? s : Case.LOWER.apply(s[0]) + s[1..-1]
}
def isNotEmpty(content) {
return content != null && content.toString().trim().length() > 0
}
static String changeStyle(String str, boolean toCamel) {
if (!str || str.size() <= 1)
return str
if (toCamel) {
String r = str.toLowerCase().split('_').collect { cc -> Case.LOWER.apply(cc).capitalize() }.join('')
return r[0].toLowerCase() + r[1..-1]
} else {
str = str[0].toLowerCase() + str[1..-1]
return str.collect { cc -> ((char) cc).isUpperCase() ? '_' + cc.toLowerCase() : cc }.join('')
}
}
//生成序列化的serialVersionUID
static String genSerialID() {
return "\tprivate static final long serialVersionUID = " + Math.abs(new Random().nextLong()) + "L;"
}
3.2、第二步:生成POJO到指定位置
一张图,自己摸索吧。
项目文件在我提供的资源可以下载看到源码。