史上最全的JPA日常使用总结

news/2024/10/22 14:36:17/

好久没写博客了,今天来总结一下JPA的日常使用吧,因为换了工作,以前是用Mybatis Plus,现在要使用JPA。

虽说是总结,也算是学习。

JPA其实按道理来说是一种规范,像Hibernate就是遵循了这种规范。现在最具代表性的就是Spring Data JPA。

闲话不多说,进入正题。

添加依赖

首先就是依赖了,添加依赖

org.springframework.boot spring-boot-starter-data-jpa 然后项目就引入了JPA。

使用配置

在application.properties中,添加下面配置

spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
#create:每次运行程序时,都会重新创建表,故而数据会丢失
#create-drop:每次运行程序时会先创建表结构,然后待程序结束时清空表
#update:每次运行程序,没有表时会创建表,如果对象发生改变会更新表结构,原有数据不会清空,只会更新(推荐使用)
#validate:运行程序会校验数据与数据库的字段类型是否相同,字段不同会报错
#none: 禁用 DDL 处理
spring.jpa.hibernate.ddl-auto=none
然后就可以使用了,不过Spring Data JPA没有代码生成器,所以从实体类到Controller都要自己来写。这一点就没有Mybatis Plus省事。

常用注解

JPA使用了大量的注解,来进行表—实体类之间的关系映射。来达到简化DAO层,达到少些SQL,甚至不写SQL就可以对数据库进行增删改查的操作。

下面就来介绍一下常用的注解:

@Entity
在类上使用,标明这个类对应数据库中的一个表。

@Table
在类上和@Entity配合使用,来设置对应的表的名字。比如@Table(name = “user”)还有其他可选的属性,如catalog和schema,不过不常用。

@SecondaryTable
在类上使用,一个实体类可以映射多个表,使用这个注解来定义单个从表的名字,主键名字等属性,不经常使用。

@SecondaryTables
这个注解在有多个@SecondaryTable时使用,用来包裹上面的@SecondaryTable。

@Id
在属性上使用,标明数据库表的主键对应的属性。

@GeneratedValue
在属性上使用,在主键属性上,来声明主键的生成策略。如@GeneratedValue(strategy=GenerationType,generator=””),

strategy:表示主键生成策略,有AUTO,INDENTITY,SEQUENCE 和 TABLE
generator:表示主键生成器的名称

Hibernate中有一个@GenericGenerator注解,和这个差不多。

@Column
在属性上使用,来声明该属性对应表中的哪个字段。 如@Column(name = “name”, unique = true, nullable = false, length = 20)

name:数据库表中该字段的名称
nullable:是否允许为null,默认为true
unique:是否是唯一标识,默认为false
length:该字段的大小,仅对String类型的字段有效
还有一些其他的属性设置,不常用就不写了。

@Transient
在属性上使用,表示该属性不是数据库表的字段的映射,即在数据库中没有这个属性对应的字段。

@OneToOne
@JoinColumn
在属性上使用,来声明一对一的表关系。和@JoinColumn一起使用。 如

@OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
@JoinColumn(name = “user_id”, referencedColumnName = “id”)
private User user;
targetEntity:表示关联的实体类
fetch:表示抓取策略,默认是懒加载,设置为FetchType.EAGER就是立即加载。
而@JoinColumn来设置数据库关联的字段。

name:列名。
referencedColumnName:该列指向列的列名
简单理解就是name是这个列在自己表中的名字,而referencedColumnName是这个列在关联的其他表的名字。

如果有多个@JoinColumn,可以使用@JoinColumns包裹起来。

@OneToMany
和上面的一样,不过是声明一对多的关系。就是一个自己包含多个其他,用List封装。如

@OneToMany(targetEntity = User.class, fetch = FetchType.EAGER)
@JoinColumn(name = “user_id”, referencedColumnName = “id”)
private List users;
@ManyToOne
和@OneToOne差不多。不过是多对一的关系。

@ManyToOne(targetEntity = User.class, fetch = FetchType.EAGER)
@JoinColumn(name = “user_id”, referencedColumnName = “id”)
private User user;
@ManyToMany
声明多对多的关系,中间表由ORM框架自动处理。如:

@Entity
public class User {
private List books;

@ManyToMany(targetEntity=package.Book.class)
public List getBooks() {
return books;
}

public void setBooks(List books) {
this.books=books;
}
}

@Entity
public class Book {
private List users;

@ManyToMany(targetEntity=package.Users.class, mappedBy=”books”)
public List getUsers() {
return users;
}

public void setUsers(List users) {
this.users=users;
}
}
targetEntity:表示多对多关联的另一个实体类的全名,例如package.Book.class
mappedBy:表示多对多关联的另一个实体类的对应集合属性名称。但是只有一个实体的@ManyToMany注解需要指定mappedBy属性,指向targetEntity的集合属性名称
@JoinTable
JoinTable在many-to-many关系的所有者一边定义。用于设置中间表。下面就是定义了一个CUST_PHONE中间表,来关联CUST和PHONE。包含两个外键,一个外键是CUST_ID,指向表CUST的主键ID,另一个外键是PHONE_ID,指向表PHONE的主键ID。

@JoinTable(
table=@Table(name=CUST_PHONE),
joinColumns=@JoinColumn(name=“CUST_ID”, referencedColumnName=“ID”),
inverseJoinColumns=@JoinColumn(name=“PHONE_ID”, referencedColumnName=“ID”)
)
@OrderBy
在一对多,多对多关系中,设置从数据库中查询的数据集合对象按照指定方式排序。如

@OneToMany(targetEntity = Book.class, cascade = CascadeType.ALL, mappedBy = “person”)
@OrderBy(name = “isbn ASC, name DESC”)
private List books = new ArrayList();
@IdClass
当使用了复合主键时,需要定义一个类作为id class。 类必须声明为public,并提供一个声明为public的空构造函数。 必须实现Serializable接口,覆写 equals() 和 hashCode()方法。entity class的所有id field在id class都要定义,且类型一样。如

public class EmployeePK implements java.io.Serializable{String empName;Integer empAge;public EmployeePK(){}public boolean equals(Object obj){ ......}public int hashCode(){......}
}@IdClass(value=com.acme.EmployeePK.class)
@Entity(access=FIELD)
public class Employee {@Id String empName;@Id Integer empAge;
}

@MapKey
在一对多,多对多关系中,我们可以用Map来保存集合对象。默认用主键值做key,如果使用复合主键,则用id class的实例做key,如果指定了name属性,就用指定的field的值做key。

@Table(name = "PERSON")
public class Person {@OneToMany(targetEntity = Book.class, cascade = CascadeType.ALL, mappedBy = "person")
@MapKey(name = "isbn")
private Map books = new HashMap();
}

@PrimaryKeyJoinColumn
在@SecondaryTable注解中使用,用于指定从表的主键。
在一对一关系中,用于指定本表主键和另一张表的关联关系。这里类似于@JoinColumn。如
@Entity
@Table(name=“CUSTOMER”)
@SecondaryTable(name=“CUST_DETAIL”,pkJoin=@PrimaryKeyJoinColumn(name=“CUST_ID”,referencedColumnName=“id”))
public class Customer {
@Id(generate = GeneratorType.AUTO)
public Integer getId() {
return id;
}
}

@Table(name = “Employee”)
public class Employee {
@OneToOne
@PrimaryKeyJoinColumn(name = “id”, referencedColumnName=“INFO_ID”)
EmployeeInfo info;
}

@PrimaryKeyJoinColumns
用来包裹@PrimaryKeyJoinColumn。

@Version
Version指定实体类在乐观事务中的version属性

@Lob
Lob指定一个属性作为数据库支持的大对象类型在数据库中存储。

@TableGenerator
TableGenerator定义一个主键值生成器

@SequenceGenerator
SequenceGenerator定义一个主键值生成器

@DiscriminatorColumn
DiscriminatorColumn定义在使用SINGLE_TABLE或JOINED继承策略的表中区别不继承层次的列。

@MappedSuperclass
可以将父类的JPA注解传递给子类,使子类能够继承父类的JPA注解。

@Embeddable
在类上使用,标明该类是可以被嵌入到其他类中的。

@Embedded
在属性上使用,把类嵌入到当前类中,当一个表对应多个实体类,可以用这个注解把类进行组合映射数据库中的表。

@Enumerated
用于标注枚举字段,当不使用任何注解的时候,默认情况下是使用ordinal属性,序号是从0开始。使用该注解之后,数据库中使用枚举类型的name属性。

@Basic
表示一个简单的属性到数据库表的字段的映射。

@Temporal
在进行属性映射时可使用@Temporal注解来调整精度。

@Temporal(TemporalType.TIMESTAMP)// 时间戳
public Date getCreatedTime() {
return createdTime;
}

@Temporal(TemporalType.DATE) //时间精确到天
public Date getBirth() {
return birth;
}
@Query
在Dao接口上使用,用来写SQL或者JPQL。如

//@Query 使用jpql的方式查询。?1代表参数的占位符,其中1对应方法中的参数索引
@Query(value=“from Customer where custName = ?1”)
public Customer findCustomer(String custName);

/**

  • nativeQuery : 使用本地sql的方式查询
    */
    @Query(value=“select * from cst_customer”,nativeQuery=true)
    public void findList();
    此外也可以通过使用 @Query 来执行一个更新操作,不过需要在使用 @Query 的同时,用 @Modifying 来将该操作标识为修改查询,这样生成一个更新的操作。

当然JPA提供了大量的查询方法可以使用,很少需要自己写SQL。

Hibernate验证注解

@Pattern
通过正则表达式来验证字符串,如@pattern(regex=”[a-z]{6}”)

@Length
验证字符串的长度,如@Length(min=3,max=20)

@Email
验证一个Email地址是否有效,如@Email

@Range
验证一个整型是否在有效的范围内,如@Range(min=0,max=100)

@Min
验证一个整型必须不小于指定值,如@Min(value=10)

@Max
验证一个整型必须不大于指定值,如@Max(value=20)

@Size
集合或数组的大小是否在指定范围内,如@Size(min=1,max=255)

JPA中的注解真是多啊,说完了注解来看一下其他的高级用法。

方法命名规则查询
除了使用@Query注解,JPA还有一种神奇的查询方式,根据方法名称进行解析,并自动生成查询语句进行查询。 规则如下:

关键字 方法命名 sql where 字句
And findByNameAndPwd where name= ? and pwd =?
Or findByNameOrSex where name= ? or sex=?
Is,Equals findById,findByIdEquals where id= ?
Between findByIdBetween where id between ? and ?
LessThan findByIdLessThan where id < ?
LessThanEquals findByIdLessThanEquals where id <= ?
GreaterThan findByIdGreaterThan where id > ?
GreaterThanEquals findByIdGreaterThanEquals where id > = ?
After findByIdAfter where id > ?
Before findByIdBefore where id < ?
IsNull findByNameIsNull where name is null
isNotNull,NotNull findByNameNotNull where name is not null
Like findByNameLike where name like ?
NotLike findByNameNotLike where name not like ?
StartingWith findByNameStartingWith where name like ‘?%’
EndingWith findByNameEndingWith where name like ‘%?’
Containing findByNameContaining where name like ‘%?%’
OrderBy findByIdOrderByXDesc where id=? order by x desc
Not findByNameNot where name <> ?
In findByIdIn(Collection<?> c) where id in (?) NotIn findByIdNotIn(Collection<?> c) where id not in (?)
True findByAaaTue where aaa = true
False findByAaaFalse where aaa = false
IgnoreCase findByNameIgnoreCase where UPPER(name)=UPPER(?)
好神奇,只要按照方法名称规则命名,不需要写SQL,JPA会自动生成SQL查询,从此不用再写SQL了。

对啦,Dao接口要继承JpaRepository 和 JpaSpecificationExecutor。如

public interface UserDao extends JpaRepository<User, Long>, JpaSpecificationExecutor {
}
那个Long是主键的类型。

可以使用JPA增强器zuji-jpa

top.spring-data-jpa zuji-jpa 1.0.1 接口就只需要继承一个BaseRepository

import com.ler.demo.domain.User;
import top.springdatajpa.zujijpa.repository.BaseRepository;

public interface UserDao extends BaseRepository {
}
还有一个增强器fenix-spring-boot-starter

com.blinkfox fenix-spring-boot-starter 2.4.2 这些都是增强JPA功能的。可以实现类似于Mybatis-Plus那种Lambda链式查询,并且优化了JPA的从数据库到实体类的投影。有兴趣的可以看看,不过列名是String类型的,需要自己写,不像Mybatis-Plus那样。

Zuji

@GetMapping(“/list”)
public Page list(ReqUserListVO params) {
Specification spec = Specifications.where(e -> {
e.eq(“userType”, params.getUserType())
.contains(“userName”, params.getUserName())
.eq(“assigneeId”, AuthHelper.currentUserId())
.or(e2 -> e2.eq(“status”, “1”).eq(“status”, “2”))
.eq(“deleted”, 0);
});
Sort sort = Sort.by(“createTime”).descending();
return repository.findAll(spec, params.pageRequest(sort));
}
Fenix
List blogs = blogRepository.findAll(builder ->
builder.andIn(“id”, ids, ids != null && ids.length > 0)
.andLike(“title”, params.get(“title”), params.get(“title”) != null)
.andLike(“author”, params.get(“author”))
.andBetween(“createTime”, params.get(“startTime”), params.get(“endTime”))
.build());

分页查询

需要使用JpaSpecificationExecutor接口中的Page findAll(Specification spec, Pageable pageable);这个方法。

root :Root接口,代表查询的根对象,可以通过root获取实体中的属性
criteriaQuery:代表一个顶层查询对象,用来自定义查询
criteriaBuilder:用来构建查询,此对象里有很多条件方法
例子:

if (start == null || start <= 0) {
start = 0;
}
if (size == null || size <= 0) {
size = 10;
}
Specification specification = (Specification) (root, criteriaQuery, criteriaBuilder)
-> criteriaBuilder.like(root.get(“name”).as(String.class), “%” + keyword + “%”);
return userDao.findAll(specification, PageRequest.of(start, size));
上面只有一个查询条件,当有多个条件时,如下:

@GetMapping(value = “/page”)
public Page page(String keyword, Integer age, Integer start, Integer size) {
if (start == null || start <= 0) {
start = 0;
}
if (size == null || size <= 0) {
size = 10;
}
List predicateList = new ArrayList<>();
Specification specification = (Specification) (root, criteriaQuery, criteriaBuilder) -> {
if (StringUtils.isNotBlank(keyword))
predicateList.add(criteriaBuilder.like(root.get(“name”).as(String.class), “%” + keyword + “%”));
if(age != null)
predicateList.add(criteriaBuilder.gt(root.get(“age”).as(Integer.class), age));
return criteriaBuilder.and(predicateList.toArray(new Predicate[0]));
};
return userDao.findAll(specification, PageRequest.of(start, size));
}
这里找了一个完整的例子:

@Override
public List getStudent(String studentNumber, String name, String nickName,
Date birthday, String courseName, float chineseScore, float mathScore,
float englishScore, float performancePoints) {
Specification specification = new Specification() {

    @Overridepublic Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {//用于暂时存放查询条件的集合List<Predicate> predicatesList = new ArrayList<>();//--------------------------------------------//查询条件示例//equal示例if (!StringUtils.isEmpty(name)) {predicatesList.add(cb.equal(root.get("name"), name));}//like示例if (!StringUtils.isEmpty(nickName)) {predicatesList.add(cb.like(root.get("nickName"), '%' + nickName + '%'));}//between示例if (birthday != null) {predicatesList.add(cb.between(root.get("birthday"), birthday, new Date()));}//关联表查询示例if (!StringUtils.isEmpty(courseName)) {Join<Student, Teacher> joinTeacher = root.join("teachers", JoinType.LEFT);predicatesList.add(cb.equal(joinTeacher.get("courseName"), courseName));}//复杂条件组合示例if (chineseScore != 0 && mathScore != 0 && englishScore != 0 && performancePoints != 0) {Join<Student, Examination> joinExam = root.join("exams", JoinType.LEFT);Predicate predicateExamChinese = cb.ge(joinExam.get("chineseScore"), chineseScore);Predicate predicateExamMath = cb.ge(joinExam.get("mathScore"), mathScore);Predicate predicateExamEnglish = cb.ge(joinExam.get("englishScore"), englishScore);Predicate predicateExamPerformance = cb.ge(joinExam.get("performancePoints"), performancePoints);//组合Predicate predicateExam = cb.or(predicateExamChinese, predicateExamMath, predicateExamEnglish);Predicate predicateExamAll = cb.and(predicateExamPerformance, predicateExam);predicatesList.add(predicateExamAll);}//--------------------------------------------//排序示例(先根据学号排序,后根据姓名排序)query.orderBy(cb.asc(root.get("studentNumber")), cb.asc(root.get("name")));//--------------------------------------------//最终将查询条件拼好然后returnPredicate[] predicates = new Predicate[predicatesList.size()];return cb.and(predicatesList.toArray(predicates));}
};
return repository.findAll(specification);

总结

JPA功能强大。当用注解关联了表之间关系后,可以自动维护表结构。不过生成环境不建议使用JPA管理表结构。 查询方式灵活,SQL,JPQL,方法命名规则查询,可以根据喜好随意搭配。

当然也有缺点,一个是没有代码生成器,还有就是太灵活了。实体类之间各种嵌入,维护起来比较麻烦。有时候一个表对应四五个实体类,然后实体类里面又嵌入其他的实体类,好几层嵌入,看着真的让人头大。

不过相比于Mybatis-Plus,需要写的SQL真的少了很多。


http://www.ppmy.cn/news/567222.html

相关文章

源码:winamp播放器 C++

winamp播放器特色 支持格式 Winamp 支持多种音频格式的回放&#xff0c;包括MP3、MP2、MIDI、MOD、AAC、FLAC、Ogg、WAV、WMA等&#xff0c;并且是 Windows 平台下首款可以默认支持 Ogg 的播放软件。 媒体库 用户安装Winamp软件时&#xff0c;软件会检索用户系统中的媒体文件…

html5实现的复古软件winamp的播放效果

Created by Wang, Jerry, last modified on Feb 25, 2015 要获取更多Jerry的原创文章&#xff0c;请关注公众号"汪子熙":

Winamp 支持.cue文件的插件CUE Navigator

安装此插件后使用Winamp播放带.cue文件的无损音乐&#xff0c;可实现整轨压缩文件的单曲上一曲&#xff0c;下一曲点播。 下载&#xff1a;http://www.ctdisk.com/file/8339556

Winamp v5.62

Winamp是数字媒体播放的先驱&#xff0c;由Nullsoft公司开发&#xff0c;创始人Justin Frankel,该软件支持MP3, MP2, MOD, S3M, MTM, ULT,XM, IT, 669, CD-Audio, Line-In等等格式。可以定制界面 Skins, 支持增强音频视觉和音频效果的 Plug-ins, 通过一些非常实用的扩展插件来增…

java mp3,audio - 在Java中播放.mp3和.wav?

audio - 在Java中播放.mp3和.wav? 如何在我的Java应用程序中播放.mp3和.wav文件? 我正在使用Swing。 我尝试在互联网上寻找类似这样的例子: public void playSound() {try {AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(new File("D:/MusicPl…

四种C#实现播放声音的方法,如DirectX ,SoundPlayer, Windows Media Player

文章来自学IT网&#xff1a;http://www.xueit.com/html/2009-09/21_4598_00.html 第一种是利用DirectX 1.安装了DirectX SDK&#xff08;有9个DLL文件&#xff09;。这里我们只用到MicroSoft.DirectX.dll 和 Microsoft.Directx.DirectSound.dll 2.引入DirectX 的DLL文件的名…

计算机用户注册表修改,WindowsServer2012 注册表修改用户的连接数

取消登陆时自动拔号: 在HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Network/RealModeNet下修改右边窗口中的 WindowsServer2012R2配置DFS&#xff0c;出现以下错误&#xff1a; 无法完成 复制文件夹想到 请参阅 错误 选项卡了解具体错误。 VBS脚本修改注册…

C#播放背景音乐的五种方法

Speech speech是一个朗读器&#xff0c;我们写一个文本text或者string&#xff0c;speech可以朗读发音&#xff0c;支持异步操作使用方便&#xff0c;推荐使用。 Speech两种使用方法 使用.NET类库和系统API: using System.Speech.Synthesis;SpeechSynthesizer synth new Spe…