参考资料 & 鸣谢:
- JPA 批注参考:https://www.oracle.com/cn/middleware/technologies/ias/toplink-jpa-annotations.html
- JPA 批注参考:https://blog.51cto.com/u_9058648/3562214
- https://blog.csdn.net/yswKnight/article/details/79257372
- https://blog.csdn.net/yiyelanxin/article/details/100107335
- https://www.cnblogs.com/banywl/articles/14004262.html
- https://blog.csdn.net/justlpf/article/details/84956212
@Entity 应用于实体类,并且使用默认的ORM规则,即 Class 名对应数据库表中表名,Class 字段名即表中的字段名(如想改变这种默认的ORM规则,就要使用 @Table 来改变 Class 名与数据库中表名的映射规则,@Column 来改变 Class 字段名与DB中表的字段名的映射规则)
元数据属性说明:
下面的代码说明,Customer类对应数据库中的Customer表,其中name为可选,缺省类名即表名!
@Entity(name="Customer")
public class Customer {}
@Table用来定义Entity主表的name,catalog,schema等属性。
元数据属性说明:
@Entity(name="Customer")
@Table(name = "tb_customer", catalog = "db_jpa", schema = "root",
uniqueConstraints={@UniqueConstraint(columnNames={"name", "age"})
})
public class Customer {}
比较:@Entity(name = “实体名”) 、@Entity @Table(name = “表名”)
参考:https://mp.weixin.qq.com/s/GYMJT5sNoJ8-KcdIVQAoNw
我们在使用JPA的时候,需要做数据表和实体类的映射,@Entity注解的实体类,默认的实体名是非限定类名,对实体名首字母小写后得到表名,例如:
@Entity
public class Comment{}
实体名:Comment,表名:comment。但实际项目中,有的表名以 tb_ 开头,那就映射不到了,那么就需要把表名写出来,有两种方式可以写:
那么哪种方式好呢?粗略一看,应该是第一种好,但是应用到JPQL语句时,未必。
例如只用@Entity定义:
// 定义实体类
@Entity(name = "tb_comment")
public class Comment{}
// JPQL 语句
@Query("update tb_comment c set c.author = ?1 where c.id= ?2 ")
这是JPQL语句,JPA一不小心就把tb_comment
认为是表名,后面的 c.xxx 就会去表中找字段,如果字段名跟实体的属性名不一样,就报错了。
如果写成下面这样的,使用@Entity + @Table(name = “表名”):
// 定义实体类
@Entity
@Table(name = "tb_comment")
public class Comment{}
// JPQL 语句
@Query("update Comment c set c.author = ?1 where c.id = ?2")
可读性好,Comment一看就是实体类名,后面的c.xxx就会去类中找属性名,但实体名不能用JPQL的保留字。
结论:
基于Column注解的columnDefinition用法:https://www.jb51.net/article/226168.htm
@Column应用于实体类属性,可以指定数据库表字段的名字和其他属性。
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface Column {
String name() default "";
boolean unique() default false;
boolean nullable() default true;
boolean insertable() default true;
boolean updatable() default true;
String columnDefinition() default "";
String table() default "";
int length() default 255;
int precision() default 0;
int scale() default 0;
}
元数据属性说明:
email
属性。(insertable、updatable属性一般多用于只读的属性,例如主键和外键等。这些字段的值通常是自动生成的)Column注解的columnDefinition用法:
columnDefinition属性表示创建表时,该字段创建的SQL语句,一般用于通过Entity生成表定义时使用,如果数据库中表已经建好,该属性没有必要使用
/**
* 1、指定字段类型、长度、是否允许null、是否唯一、默认值
*/
@Column(name = "code",columnDefinition = "Varchar(100) not null default'' unique")
private String code;
/**
* 2、需要特殊指定字段类型的情况
* String默认映射类型为VARCHAR, 如果要将String类型映射到特定数据库的BLOB或TEXT字段类型
*/
@Column(name = "text",columnDefinition="TEXT")
private String text;
@Column(name = "salary", columnDefinition = "decimal(5,2)")
private BigDecimal salary;
@Column(name="birthday",columnDefinition="date")
private Date birthday;
@Column(name="createTime",columnDefinition="datetime")
private Date createTime;
应用于实体类属性,表明该字段是要映射到数据库表,@Entity标注的实体类,所有属性默认为@Basic(所以默认该注解可以不加)
元数据属性说明:
FetchType.LAZY
(延迟加载)和FetchType.EAGER
(立即加载),默认:EAGER
PS 注意:@Basic(fetch =FetchType.LAZY) 标注某属性时,表示只有调用Hibernate对象的该属性的get方法时,才会从数据库表中查找对应该属性的字段值
@Transient作用在类属性上,与@Basic作用相反,表明该属性不需要持久化,JPA映射数据时忽略该属性
@Temporal作用在类属性上,用来设置Date类型的属性映射到数据库时的精度。当我们使用到 java.util 包中的时间日期类型,则需要此注释来说明转化成 java.util包中的类型。
java.sql.Date
java.sql.Time
java.sql.Timestamp
PS 注意:@Temporal注解只能映射java.util.Date or java.util.Calendar
,这两种时间类型,否则将报如下错误:
Hibernate.AnnotationException: @Temporal should only be set on a java.util.Date or java.util.Calendar property: entity.User.createLocalDateTime
1、首先定义实体类,并给需要处理的时间字段加上@Temporal注解。示例代码:
@Data
@Entity
@Table(name = "sys_user")
public class User implements Serializable {
@Id
private Long id;
@Temporal(TemporalType.DATE)
private java.util.Date createDate;
@Temporal(TemporalType.TIME)
private java.util.Date createTime;
@Temporal(TemporalType.TIMESTAMP)
private java.util.Date createTIMESTAMP;
// @Temporal只能映射 ava.util.Date or java.util.Calendar 两种时间类型
// @Temporal(TemporalType.TIMESTAMP)
// private java.time.LocalDateTime createLocalDateTime;
}
2、启动项目或者测试类,查看建表语句
create table sys_user (
id bigint not null,
createDate date,
createTIMESTAMP timestamp,
createTime time,
primary key (id)
)
3、测试代码
import entity.User;
import org.junit.Test;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class TestDemo {
@Test
public void testSave() {
// 1.加载配置文件创建工厂(实体管理器工厂)对象
EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
// 2.通过实体管理器工厂获取实体管理器
EntityManager em = factory.createEntityManager();
// 3.获取事务对象,开启事务
EntityTransaction tx = em.getTransaction();
// 开启事物新增数据,手动设置时间
tx.begin();
User user = new User();
user.setId(1L);
user.setCreateDate(new java.util.Date());
user.setCreateTime(new java.util.Date());
user.setCreateTIMESTAMP(new java.util.Date());
em.persist(user);
tx.commit();
em.refresh(user);
User user_selelct = em.find(User.class, 1L);
System.out.println(user_selelct);
// 6.释放资源
em.close();
factory.close();
}
}
4、查看输出日志
User(id=1, createDate=2022-02-01, createTime=15:13:36, createTIMESTAMP=2022-02-01 15:13:36.325)
@CreationTimestamp 与 @UpdateTimestamp 注解是JPA用来自动创建、更新时间戳
使用该注解可以让Hibernate JPA在插入时针对注解的属性对应的日期类型创建默认值。此批注没有属性。
/**
* Marks a property as the creation timestamp of the containing entity. The property value will be set to the current
* VM date exactly once when saving the owning entity for the first time.
*
* Supported property types:
*
* - {@link java.util.Date}
* - {@link java.util.Calendar}
* - {@link java.sql.Date}
* - {@link java.sql.Time}
* - {@link java.sql.Timestamp}
* - {@link java.time.Instant}
* - {@link java.time.LocalDate}
* - {@link java.time.LocalDateTime}
* - {@link java.time.LocalTime}
* - {@link java.time.MonthDay}
* - {@link java.time.OffsetDateTime}
* - {@link java.time.OffsetTime}
* - {@link java.time.Year}
* - {@link java.time.YearMonth}
* - {@link java.time.ZonedDateTime}
*
*
* @author Gunnar Morling
*/
@ValueGenerationType(generatedBy = CreationTimestampGeneration.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface CreationTimestamp {
}
示例代码参考@UpdateTimestamp
使用该注解可以让Hibernate JPA在更新时时针对注解的属性对应的日期类型创建默认值。此批注没有属性。
/**
* Marks a property as the update timestamp of the containing entity. The property value will be set to the current VM
* date whenever the owning entity is updated.
*
* Supported property types:
*
* - {@link java.util.Date}
* - {@link java.util.Calendar}
* - {@link java.sql.Date}
* - {@link java.sql.Time}
* - {@link java.sql.Timestamp}
* - {@link java.time.Instant}
* - {@link java.time.LocalDate}
* - {@link java.time.LocalDateTime}
* - {@link java.time.LocalTime}
* - {@link java.time.MonthDay}
* - {@link java.time.OffsetDateTime}
* - {@link java.time.OffsetTime}
* - {@link java.time.Year}
* - {@link java.time.YearMonth}
* - {@link java.time.ZonedDateTime}
*
*
* @author Gunnar Morling
*/
@ValueGenerationType(generatedBy = UpdateTimestampGeneration.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface UpdateTimestamp {
}
1、一般情况下创建、更新时间戳注解 @CreationTimestamp、@UpdateTimestamp 会一起使用。下面是代码示例:
@Data
@Entity
@Table(name = "sys_user")
public class User implements Serializable {
@Id
private Long id;
@CreationTimestamp
private java.util.Date createDate;
@UpdateTimestamp
private java.sql.Timestamp updateTimestamp;
@CreationTimestamp
private java.time.LocalDate createLocalDate;
@UpdateTimestamp
private java.time.LocalTime updateLocalTime;
@UpdateTimestamp
private java.time.LocalDateTime updateLocalDateTime;
}
2、启动项目或者测试类,查看JPA自动建表语句。可以看到他们的对应关系,使用时需要特别注意。
create table sys_user (
id bigint not null,
createDate timestamp,
createLocalDate date,
updateLocalDateTime timestamp,
updateLocalTime time,
updateTimestamp timestamp,
primary key (id)
)
3、运行单元测试,查看手动设置时间与JPA自动设置时间的区别
import entity.User;
import org.junit.Test;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Date;
public class TestDemo {
@Test
public void testSave() {
// 1.加载配置文件创建工厂(实体管理器工厂)对象
EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
// 2.通过实体管理器工厂获取实体管理器
EntityManager em = factory.createEntityManager();
// 3.获取事务对象,开启事务
EntityTransaction tx = em.getTransaction();
// 开启事物新增数据,手动设置时间
tx.begin();
User user = new User();
user.setId(1L);
user.setCreateDate(new Date());
user.setCreateLocalDate(LocalDate.now());
user.setUpdateTimestamp(new Timestamp(System.currentTimeMillis()));
user.setUpdateLocalTime(LocalTime.now());
user.setUpdateLocalDateTime(LocalDateTime.now());
em.persist(user);
tx.commit();
em.refresh(user);
User user_selelct = em.find(User.class, 1L);
System.out.println(user_selelct);
// 开启事物新增数据,JPA自动设置时间
tx.begin();
User user2 = new User();
user2.setId(2L);
em.persist(user2);
tx.commit();
em.refresh(user2);
User user_selelct2 = em.find(User.class, 2L);
System.out.println(user_selelct2);
// 6.释放资源
em.close();
factory.close();
}
}
4、查看输出日志
User(id=1, createDate=2022-01-31 21:59:16.181, updateTimestamp=2022-01-31 21:59:16.181, createLocalDate=2022-01-31, updateLocalTime=21:59:16, updateLocalDateTime=2022-01-31T21:59:16.181568)
User(id=2, createDate=2022-01-31 21:59:16.245, updateTimestamp=2022-01-31 21:59:16.245, createLocalDate=2022-01-31, updateLocalTime=21:59:16, updateLocalDateTime=2022-01-31T21:59:16.245574)
参考文献 & 鸣谢:
- JPA注解@CreationTimestamp和@UpdateTimestamp:https://blog.csdn.net/z185665096/article/details/106090223
- JPA设置默认值、Timestamp设置、自动获取时间:https://blog.csdn.net/ctwy291314/article/details/88250205
比如,用户名,插入时可以根据当前线程直接获取,然后利用注解赋值,这样在业务处理时就可以不用关注该属性。类似还有更新时自动赋值操作人等。
/**
* Hibernate新增插入时自动填充
*/
@ValueGenerationType(
generatedBy = CreationUserGeneration.class
)
@Retention(RetentionPolicy.RUNTIME)
public @interface CreationUser {
}
import org.hibernate.tuple.AnnotationValueGeneration;
import org.hibernate.tuple.GenerationTiming;
import org.hibernate.tuple.ValueGenerator;
/**
* 针对加了注解{@link @CreationUser}的属性在插入时,自动根据当前线程获取用户信息赋值
*/
public class CreationUserGeneration implements AnnotationValueGeneration<CreationUser> {
private ValueGenerator<?> generator;
public CreationUserGeneration() {
}
@Override
public void initialize(CreationUser annotation, Class<?> propertyType) {
// 主要逻辑在这里实现
this.generator = (ValueGenerator<Object>) (session, o) -> UserContextHolder.getUserName();
}
@Override
public GenerationTiming getGenerationTiming() {
// 只有插入时,每次都修改使用GenerationTiming.ALWAYS
return GenerationTiming.INSERT;
}
@Override
public ValueGenerator<?> getValueGenerator() {
return this.generator;
}
@Override
public boolean referenceColumnInSql() {
return false;
}
@Override
public String getDatabaseGeneratedReferencedColumnValue() {
return null;
}
}
就这样就OK了,注解@CreationUser就可以直接在实体类使用了。
@Id 定义了映射到数据库表的主键的属性,一个实体只能有一个属性被映射为主键,可置于getXxxx()前。
当实体类使用复合主键时,需要定义一个类作为 ID 实体类。作为联合主键类,需要满足以下要求:
元数据属性说明:
假设user_article表中的联合主键是 title 与create_user_id,联合主键类代码如下:
@Data
public class UserArticleKey implements Serializable {
private String title;
private Long createUserId;
public UserArticleKey() {
}
public UserArticleKey(String title, String content, Long createUserId) {
this.title = title;
this.createUserId = createUserId;
}
}
user_article表实体类如下:
@Data
@Entity
@Table(name="user_article")
@IdClass(value = UserArticleKey.class)
public class UserArticle {
private Integer id;
@Id
private String title;
@Id
private Long createUserId;
}
使用 @EmbeddedId 批注指定一个由实体拥有的可嵌入复合主键类(通常由两个或更多基元类型或 JDK 对象类型组成)。从原有数据库映射时(此时数据库键由多列组成),通常将出现复合主键。(或者也可以使复合主键类(IdClass)成为非嵌入类)
复合主键类具有下列特征:
它是一个普通的旧式 Java 对象 (POJO) 类。
它必须为 public,并且必须有一个 public 无参数构造函数。
如果使用基于属性的访问,则主键类的属性必须为 public 或 protected。
它必须是可序列化的。
它必须定义 equals 和 hashCode 方法。
这些方法的值相等性的语义必须与键映射到的数据库类型的数据库相等性一致。
此批注没有属性,一般与 @Embeddable(注解在类上,表示此类是可以被其他类嵌套)配合使用。
@Embeddable 还能多层嵌套使用
假设sys_user表中的联合主键是 id 与 username,联合主键类代码如下:
1、主键的实体类
@Data
@Embeddable
public class UserPK implements Serializable {
@Column(name = "id")
private Long id;
@Column(name = "username")
private String username;
}
2、表映射的持久类(持久类里要有一个注解@EmbeddedId修饰主键的成员变量,其他的和普通的Entity类一样。它所对应复合主键类需要使用@Embeddable注解)
@Data
@Entity
@Table(name = "sys_user")
public class User implements Serializable {
@EmbeddedId
private UserPK userPK;
private String password;
}
3、启动项目或者测试类,查看JPA自动建表语句,可以发现把 id 与 usrname 创建成联合主键了
create table sys_user (
id bigint not null,
username varchar(255) not null,
password varchar(255),
primary key (id, username)
)
4、普通Java项目测试
import entity.User;
import entity.UserPK;
import org.junit.Test;
import javax.persistence.*;
public class TestDemo {
@Test
public void testSave() {
// 1.加载配置文件创建工厂(实体管理器工厂)对象
EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
// 2.通过实体管理器工厂获取实体管理器
EntityManager em = factory.createEntityManager();
// 3.获取事务对象,开启事务
EntityTransaction tx = em.getTransaction();
tx.begin();
UserPK userPK = new UserPK();
userPK.setId(1L);
userPK.setUsername("admin");
User user = new User();
user.setUserPK(userPK);
user.setPassword("password");
em.persist(user);
tx.commit();
em.refresh(user);
User user_selelct = em.find(User.class, userPK);
System.out.println(user_selelct);
// 6.释放资源
em.close();
factory.close();
}
}
输出日志:
Hibernate:
insert
into
sys_user
(password, id, username)
values
(?, ?, ?)
Hibernate:
select
user0_.id as id1_0_0_,
user0_.username as username2_0_0_,
user0_.password as password3_0_0_
from
sys_user user0_
where
user0_.id=?
and user0_.username=?
User(userPK=UserPK(id=1, username=admin), password=password)
5、SpringBoot项目测试,查看UserRepository接口定义查询方法
@Repository
public interface UserRepository extends JpaRepository<User, UserPK> {
// 只查找一个条件(只查询复合主键中的一个字段)
List<User> findByIdUsername(String username);
// 查找两个条件(如果有三个复合主键可以这样使用,如果只有两个复合主键就直接findById就行了)
List<User> findByIdIdAndUsername(String id,String username);
// 查询所有复合主键就直接findById就行
User findById(UserPK, userpk);
}
请参考本人的其他篇章,Hibernate JPA 主键策略的所有文章
@GeneratedValue主要与@Id一同使用,定义主键的生成策略,通过strategy
属性指定。
元数据属性说明:
@javax.persistence.Id
@javax.persistence.GeneratedValue(generator="xxx",strategy=GenerationType.AUTO)
strategy:表示主键生成策略,有如下四种方式:
方式一:@GeneratedValue(strategy=GenerationType.AUTO) 默认策略,生成方式取决于底层的数据库。
方式二:@GeneratedValue(strategy = GenerationType.IDENTITY)指定“自动增长”策略,适用于MySQL。
方式三:@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_tbl_user")
指定“序列”策略,适用于Oracle。其中generator表示生成器的名字,这个属性通常和ORM框架相关。
例如,Hibernate可以指定uuid等主键生成方式(要和@SequenceGenerator(name = "seq_tbl_user",
sequenceName = "seq_tbl_user", allocationSize = 1)注解配合使用,其中name指定生成器的名
字(与generator的值一样),sequenceName指定数据库中定义序列的名字,allocationSize指定序
列每次增长1 )
方式四:@GeneratedValue(strategy=GenerationType.TABLE) 使用一个特定的数据库表格来保存主键
请参考本人的其他篇章,Hibernate JPA 主键策略:SEQUENCE 策略
请参考本人的其他篇章,Hibernate JPA 主键策略:自定义主键生成器
请参考本人的其他篇章,Hibernate JPA 主键策略:TABLE 策略
@Embedded 与 @Embeddable:当一个实体类要在多个不同的实体类中进行使用,而其不需要生成数据库表
- @Embeddable:注解在类上,表示此类是可以被其他类嵌套
- @Embedded:注解在属性上,表示嵌套被@Embeddable注解的同类型类
@AttributeOverride、@AttributeOverrides 注解重定义其父类或者嵌套类属性映射到数据库表中字段
默认情况下,JPA 持续性提供程序假设每个实体均持久保存到它自己的数据库表。
使用 @Embeddable 批注指定一个类,该类的实例存储为拥有实体的固有部分并共享该实体的身份。嵌入对象的每个持久属性或字段都将映射到实体的数据库表。此批注没有属性。
下面使用此注解批注 实体类UserCommon.java,然后可以使用 @Embedded 或 @EmbeddedId 把UserCommon 注入另一个实体类中当做其属性。(可以理解为单独抽离几个字段出来独立到另一个类中)
@Data
@Embeddable
public class UserCommon {
@Column(name = "startDate")
private java.util.Date startDate;
private java.util.Date endDate;
}
PS:Embeddable一般与Embedded或EmbeddedId组合使用:@Embeddable + @Embedded、@Embeddable + @EmbeddedId
使用 @Embedded 批注指定一个持久字段,该字段必须为引用类型,且还被 @Embeddable 批注。默认情况下 @Embeddable 类中指定的列定义适用于 @Embedded 类。如果要覆盖这些列定义可以使用 @AttributeOverride。
此批注没有属性。下面使用@Embeddable + @Embedded组合定义代码示例:
1、使用@Embeddable定义公共实体类
@Data
@Embeddable
public class UserCommon {
@Column(name = "startDate")
private java.util.Date startDate;
private java.util.Date endDate;
}
2、表映射的持久类(持久类里要有一个注解@Embedded修饰主键的成员变量,其他的和普通的Entity类一样。它所对应复合主键类需要使用@Embeddable注解)
@Data
@Entity
@Table(name = "sys_user")
public class User implements Serializable {
@EmbeddedId
private UserPK userPK;
@Embedded
private UserCommon userCommon;
private String password;
}
3、启动项目或者测试类,查看JPA自动建表语句,可以发现把 startDate 与 endDate 也创建到实体类 User 映射的数据表中了
create table sys_user (
id bigint not null,
username varchar(255) not null,
password varchar(255),
endDate timestamp,
startDate timestamp,
primary key (id, username)
)
备注:正常用法一般都是 @Embedded + @Embeddable 同时使用,实际上单独使用任意一个,效果也是一样(可读性差)
@MappedSuperclass:(很重要)实现将实体类的多个属性分别封装到不同的非实体类中
- 注解的类将不是完整的实体类,不会映射到数据库表,但其属性将映射到子类的数据库字段
- 注解的类不能再标注@Entity或@Table注解,也无需实现序列化接口
- 注解的类继承另一个实体类或标注@MappedSuperclass类,他可使用@AttributeOverride或@AttributeOverrides注解重定义其父类属性映射到数据库表中字段
当我们在定义多个实体类时,可能会遇到这几个实体类都有几个共同的属性,这时就会出现很多重复代码。 这时我们可以选择用注解@MappedSuperclass。编写一个父类(基类),将这些共同属性放到这个父类中,并且在父类上加上@MappedSuperclass注解。
使用方法和注意事项:
该批注没有属性。可以在子类中使用 @AttributeOverride 或 @AssociationOverride 批注来覆盖超类的映射配置。
@Data
@Entity
@Table(name = "sys_user")
public class User extends BaseEntity implements Serializable {
private String admin;
private String password;
}
@Data
@MappedSuperclass
abstract class BaseEntity {
@Id
private Long id; // 数据库主键
@Column(name = "creation_time")
private java.util.Date creationTime; //创建时间
@Column(name = "update_time")
private java.util.Date updateTime; //修改时间
}
启动项目或者测试类,查看JPA自动建表语句,可以发现 id、creation_time、update_time 字段也新增了
create table sys_user (
id bigint not null,
creation_time timestamp,
update_time timestamp,
admin varchar(255),
password varchar(255),
primary key (id)
)
@AttributeOverride 批注是用来覆盖 @Embeddable 类或者 @MappedSuperclas 类中字段的列属性 (包含要覆盖的@Embeddable
类中字段名name和新增的@Column
字段的属性;)
元数据属性说明:
1、下面使用 @AttributeOverride 注解重写 @Embeddable 批注的类中的属性字段。为了方便查看使用了内部类。
@Data
@Entity
@Table(name = "sys_user")
public class User implements Serializable {
@EmbeddedId
private UserPK userPK;
@Embedded
@AttributeOverrides({ //把属性映射的列名称进行重写,startDate 和 endDate 是UserCommon类中的字段
@AttributeOverride(name = "startDate", column = @Column(name = "start_date", nullable = false)),
@AttributeOverride(name = "endDate", column = @Column(name = "end_date", nullable = false))
})
private UserCommon userCommon;
private String password;
@Data
@Embeddable
public static class UserCommon implements Serializable {
@Column(name = "startDate")
private java.util.Date startDate;
private java.util.Date endDate;
}
@Data
@Embeddable
public static class UserPK implements Serializable {
@Column(name = "id")
private Long id;
@Column(name = "username")
private String username;
}
}
2、启动项目或者测试类,查看JPA自动建表语句,可以发现startDate与endDate字段都被重写了,并且加了不能为空的约束。
create table sys_user (
id bigint not null,
username varchar(255) not null,
password varchar(255),
end_date timestamp not null,
start_date timestamp not null,
primary key (id, username)
)
3、下面使用 @AttributeOverride 注解重写 @MappedSuperclass 批注的类中的属性字段。
@Data
@Entity
@Table(name = "sys_user")
@AttributeOverrides({ // 把属性映射的列名称进行重写,creationTime 和 updateTime 是BaseEntity类中的字段
@AttributeOverride(name = "creationTime", column = @Column(name = "create_date", nullable = false)),
@AttributeOverride(name = "updateTime", column = @Column(name = "update_date", nullable = false))
})
public class User extends BaseEntity implements Serializable {
private String admin;
private String password;
}
@Data
@MappedSuperclass
abstract class BaseEntity {
@Id
private Long id; // 数据库主键
@Column(name = "creation_time")
private java.util.Date creationTime; //创建时间
@Column(name = "update_time")
private java.util.Date updateTime; //修改时间
}
4、启动项目或者测试类,查看JPA自动建表语句,可以发现creationTime与updateTime字段都被重写了,并且加了不能为空的约束。
create table sys_user (
id bigint not null,
create_date timestamp not null,
update_date timestamp not null,
admin varchar(255),
password varchar(255),
primary key (id)
)
@AttributeOverrides 里面只包含了@AttributeOverride 类型数组,用法直接参考@AttributeOverride的即可。
默认情况下,JPA 持续性提供程序自动假设子类继承超类中定义的持久属性及其关联映射。
如果继承的列定义对实体不正确(例如:继承的列名与已经存在的数据模型不兼容或作为数据库中的列名无效),请使用 @AssociationOverride 批注自定义从 @MappedSuperclass 或 @Embeddable 继承的 @OneToOne 或 @ManyToOne 映射,以更改与字段或属性关联的 @JoinColumn。如果有多个要进行的 @AssociationOverride 更改,则必须使用 @AssociationOverrides。
要自定义基本映射以更改它的 @Column,请使用 @AttributeOverride。
元数据属性说明:
1、这里使用@MappedSuperclass示例,先测试没有使用@AssociationOverride注解的效果。
import lombok.Data;
import javax.persistence.*;
@Data
@Entity
@Table(name = "sys_user")
public class User extends BaseEntity implements java.io.Serializable {
@Id
private Long id;
private String admin;
private String password;
}
@Data
@MappedSuperclass
abstract class BaseEntity {
@ManyToOne
@JoinColumn(name="address_id")
private Address address;
}
@Data
@Entity
@Table(name = "sys_address")
class Address implements java.io.Serializable {
@Id
@Column(name = "address_id")
private Long addressId;
private String country;
}
查看输出日志:
create table sys_address (
address_id bigint not null,
country varchar(255),
primary key (address_id)
);
create table sys_user (
id bigint not null,
admin varchar(255),
password varchar(255),
address_id bigint,
primary key (id)
);
alter table sys_user
add constraint FK9abddsodne05nqpe2if8gjekl
foreign key (address_id)
references sys_address;
2、再使用@MappedSuperclass + AssociationOverride示例查看效果。
import lombok.Data;
import javax.persistence.*;
@Data
@Entity
@Table(name = "sys_user")
@AssociationOverride(name="address", joinColumns=@JoinColumn(name="fk_address_id"))
public class User extends BaseEntity implements java.io.Serializable {
@Id
private Long id;
private String admin;
private String password;
}
@Data
@MappedSuperclass
abstract class BaseEntity {
@ManyToOne
@JoinColumn(name="address_id")
private Address address;
}
@Data
@Entity
@Table(name = "sys_address")
class Address implements java.io.Serializable {
@Id
private Long id;
private String country;
}
查看输出日志:
create table sys_address (
id bigint not null,
country varchar(255),
primary key (id)
);
create table sys_user (
id bigint not null,
admin varchar(255),
password varchar(255),
fk_address_id bigint,
primary key (id)
);
alter table sys_user
add constraint FKmas1faywy7oyb4vr78hhm6kp0
foreign key (fk_address_id)
references sys_address;
从日志中可以看出@AssociationOverride的作用主要是重写了@JoinColumn注解的属性。
@AssociationOverrides 里面只包含了@AssociationOverride 类型数组,用法直接参考@AssociationOverride的即可。
简单对比@AssociationOverride与AttributeOverride的作用与区别:
- @AssociationOverride主要重写@MappedSuperclass 或 @Embeddable 继承的 @JoinColumn 注解字段
- @AttributeOverride主要重写@MappedSuperclass 或 @Embeddable 继承的 @Column 注解字段
@SecondaryTable将一个实体映射到多个数据库表中。
元数据属性说明:
// 如果只多映射一个数据表的话可以只用@SecondaryTable
@Entity
@Table(name = "tb_customer")
@SecondaryTables({
@SecondaryTable(name = "tb_address", pkJoinColumns = {@PrimaryKeyJoinColumn(name="address_id",referencedColumnName="id")}),
@SecondaryTable(name = "tb_comments", pkJoinColumns = {@PrimaryKeyJoinColumn(name="comments_id",referencedColumnName="id")})
})
public class Customer implements Serializable {
@Id
private Long id;
private String username;
private String password;
@Column(table = "tb_address")
private String street;
@Column(table = "tb_address")
private String city;
@Column(table = "tb_address")
private String conutry;
@Column(table = "tb_comments")
private String title;
@Column(table = "tb_comments")
private String comments;
}
@Column中的table属性的值指定属性存储的哪张数据库表。没有用@Column中table值注解改变的属性,将会存在于默认tb_forum主表中。
mysql> desc tb_customer;
+----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| id | bigint(20) | NO | PRI | NULL | |
| password | varchar(255) | YES | | NULL | |
| username | varchar(255) | YES | | NULL | |
+----------+--------------+------+-----+---------+-------+
3 rows in set (0.02 sec)
mysql> desc tb_address;
+------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| city | varchar(255) | YES | | NULL | |
| conutry | varchar(255) | YES | | NULL | |
| street | varchar(255) | YES | | NULL | |
| address_id | bigint(20) | NO | PRI | NULL | |
+------------+--------------+------+-----+---------+-------+
4 rows in set (0.02 sec)
mysql> desc tb_comments;
+-------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+-------+
| comments | varchar(255) | YES | | NULL | |
| title | varchar(255) | YES | | NULL | |
| comments_id | bigint(20) | NO | PRI | NULL | |
+-------------+--------------+------+-----+---------+-------+
3 rows in set (0.03 sec)
当一个实体类映射到一个主表和多个从表时,用@SecondaryTables来定义各个从表的属性。
元数据属性说明:
案例可以参考上面@SecondaryTable的示例代码。
@PrimaryKeyJoinColumn一般在三种情况下会用到(@PrimaryKeyJoinColumns是用来装@PrimaryKeyJoinColumn数组的)
元数据属性说明:
下面的代码说明Customer映射到两个表,主表tb_customer,从表tb_address,从表需要建立主键列address_id,该列和主表的主键列id除了列名不同,其他定义一样
@Entity
@Table(name = "tb_customer")
@SecondaryTable(name = "tb_address", pkJoinColumns = {@PrimaryKeyJoinColumn(name="address_id",referencedColumnName="id")})
public class Customer implements Serializable {
@Id
private Long id;
private String username;
private String password;
@Column(table = "tb_address")
private String street;
@Column(table = "tb_address")
private String city;
@Column(table = "tb_address")
private String conutry;
}
下面的代码说明Customer和Address是一对一关系,Customer的主键列id作为外键指向Address的主键列address_id(这个本人暂时还没研究清楚)
@Entity
@Table(name = "tb_customer")
public class Customer {
private Long customer_id;
@OneToOne
@PrimaryKeyJoinColumn(name = "customer_id", referencedColumnName="address_id")
private Address address;
}
如果实体类使用了复合主键,指定单个PrimaryKeyJoinColumn不能满足要求时,可以用PrimaryKeyJoinColumns来定义多个PrimaryKeyJoinColumn
元数据属性说明:
下面的代码说明了Employee和EmployeeInfo是一对一关系。他们都使用复合主键,建表时需要在Employee表建立一个外键,从Employee的主键列id,name指向EmployeeInfo的主键列INFO_ID和INFO_NAME
@Data
@IdClass(EmpPK.class)
@Entity
@Table(name = "EMPLOYEE")
public class Employee {
private int id;
private String name;
private String address;
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumns({
@PrimaryKeyJoinColumn(name="id", referencedColumnName="INFO_ID"),
@PrimaryKeyJoinColumn(name="name" , referencedColumnName="INFO_NAME")})
EmployeeInfo info;
}
@Data
@IdClass(EmpPK.class)
@Entity
@Table(name = "EMPLOYEE_INFO")
public class EmployeeInfo {
@Id
@Column(name = "INFO_ID")
private int id;
@Id
@Column(name = "INFO_NAME")
private String name;
}
@UniqueConstraint定义在@Table或@SecondaryTable元数据里,用来指定建表时需要建唯一约束的列。
元数据属性说明:
@Entity
@Table(name="tb_person",uniqueConstraints={@UniqueConstraint(columnNames={"person_id", "person_name"})})
public class Person {
@Id
private Long person_id;
private String person_name;
}
@OrderBy 在加载数据的时候可以为其指定顺序。有时我们希望从数据库加载出来的集合对象是按一定方式排序的,这可以通过OrderBy来实现,默认是按对象的主键升序排列。
元数据属性说明:
fieldName1 [ASC|DESC],fieldName2 [ASC|DESC],...,
(排序类型可以不指定,默认是ASC)下面的代码说明Person和Book之间是一对多关系。集合books按照Book的age升序,name降序排列。
@Table(name="tb_person")
public class Person {
@OneToMany(targetEntity = Book.class, cascade = CascadeType.ALL, mappedBy = "person")
@OrderBy(name = "age asc, name desc")
private List<Book> books = new ArrayList();
@OneToMany(targetEntity = Detail.class, cascade = CascadeType.ALL, mappedBy = "person")
@OrderBy // 默认使用主键排序
private List<Detail> details = new ArrayList();
}
@Version 指定实体类在乐观事务中的 version 属性。在实体类重新由EntityManager管理并且加入到乐观事务中时,保证完整性。每一个类只能有一个属性被指定为 version,version 属性应该映射到实体类的主表上。version支持(int,Integer,short,Short, long,Long,java.sql.Timestamp)类型的属性或字段。
使用起来非常方便,我们只需要在实体中添加一个字段,并添加@Version注解就可以了。加了@Version后,insert和update的SQL语句都会带上version的操作。当乐观锁更新失败的时候,会抛出异常org.springframework.orm.ObjectOptimisticLockingFailureException。我们自己进行业务处理。
@Version
@Column(name = "customer_version", columnDefinition="bigint default 0")
private Long version;
@Lob指定一个属性作为数据库支持的大对象类型在数据库中存储。使用LobType这个枚举来定义Lob是二进制类型还是字符类型。
元数据属性说明(较老的版本):
新版本中该注解中没有任何参数了。下面的代码测试:
@Lob
private byte[] picture; // 默认是立即加载
@Lob
@Basic(fetch = FetchType.LAZY) // 修改成懒加载
private String description;
PS:Clob、Blob占用内存空间较大,一般配合@Basic(fetch=FetchType.LAZY)将其设置为延迟加载
@Enumerated虽然不常用却很重要,使用此注解映射枚举字段,以String类型或者Interger类型存入数据库
注入数据库的类型有两种:
// Sex为枚举类
@Column
@Enumerated(EnumType.STRING)
private Sex sex;
@ElementCollection集合映射,请参考:https://mp.weixin.qq.com/s/K8ori0frTguph18nrcVPAQ
@NoRepositoryBean:一般用作父类的repository,有这个注解,Spring不会去实例化该repository。
参考文献 & 鸣谢:https://blog.csdn.net/qq_28804275/article/details/84801457
@CreatedDate、@CreatedBy、@LastModifiedDate、@LastModifiedBy:表示字段为:创建时间字段(insert自动设置)、创建用户字段(insert自动设置)、最后修改时间字段(update自定设置)、最后修改用户字段(update自定设置)
@CreatedDate、@CreatedBy、@LastModifiedDate、@LastModifiedBy 注解的用法:
如下是操作示例:
1、在实体类上加上注解 @EntityListeners(AuditingEntityListener.class),在相应的字段上添加对应的时间和用户注解 @CreatedDate、@LastModifiedDate、@CreatedBy、@LastModifiedBy(PS:注意:日期类型可以用 Date 也可以是 Long)
package com.example.jpa.entity;
import lombok.Data;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.io.Serializable;
@Data
@Entity
@Table(name = "sys_user")
@EntityListeners(AuditingEntityListener.class)
public class User implements Serializable {
@Id
private Long id;
@CreatedDate
@Column(updatable = false, nullable = false)
private java.util.Date createTime; // 创建时间
@LastModifiedDate
@Column(nullable = false)
private Long updateTime; // 更新时间
@CreatedBy
private String createBy; // 创建人
@LastModifiedBy
private String lastModifiedBy; // 最后修改人
}
2、配置实现AuditorAware接口,以获取用户字段需要插入的信息:
package com.example.jpa.config;
import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;
import java.util.Optional;
/**
* @ClassName: JpaAuditorAware
* @Description:根据你需要返回的类型修改这个T,比如我需要返回的是字符串,就是String。需要注意的是,类需要加上@Component以便Spring扫描到,否则不起作用。
*/
@Component
public class JpaAuditorAware implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
String userId = null; // SecurityUtils.getCurrentUserId(); 正常项目安装项目逻辑获取用户ID
if (userId != null) {
return Optional.of(userId);
} else {
return Optional.of("admin_user");
}
}
}
3、在启动类中添加注解@EnableJpaAuditing,注意如果有多个AuditorAware接口实现类,需要在@EnableJpaAuditing内指定实现类
@SpringBootApplication
@EnableJpaAuditing
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
4、启动项目和单元测试查看效果
@Test
@Transactional // 在测试类对于事务提交方式默认的是回滚
@Rollback(false) // 取消自动回滚
public void save() {
User user = new User();
user.setId(1L);
User save = userRepository.save(user);
System.out.println(save);
}
5、查看输出日志
Hibernate:
select
user0_.id as id1_0_0_,
user0_.create_by as create_b2_0_0_,
user0_.create_time as create_t3_0_0_,
user0_.last_modified_by as last_mod4_0_0_,
user0_.update_time as update_t5_0_0_
from
sys_user user0_
where
user0_.id=?
User(id=1, createTime=Sat Feb 05 17:07:15 CST 2022, updateTime=1644052035337, createBy=admin_user, lastModifiedBy=admin_user)
Hibernate:
insert
into
sys_user
(create_by, create_time, last_modified_by, update_time, id)
values
(?, ?, ?, ?, ?)
1.配置多表联系注解介绍
@OneToOne(一对一) 一对一映射
targetEntityClass: 指定另一方类的字节码
cascade: 级联操作
CascadeType.MERGE 级联更新
CascadeType.PERSIST 级联保存
CascadeType.REFRESH 级联刷新
CascadeType.REMOVE 级联删除
CascadeType.ALL 级联上述4种操作
fetch: 抓取策略
FetchType.LAZY: 延迟加载(默认)
FetchType.EAGER: 迫切查询(多表连接查询)
mappedBy: 放弃外键维护
orphanRemoval: 是否使用孤儿删除
@OneToMany(一对多)
targetEntityClass: 指定多方类的字节码
cascade: 指定要使用的级联操作
fetch: 指定是否采用延迟加载
mappedBy: 指定从表实体类中引用主表对象的名称
orphanRemoval: 是否使用孤儿删除
@ManyToOne(多对一)
targetEntityClass: 指定一的一方实体类字节码
cascade: 指定要使用的级联操作
fetch: 指定是否采用延迟加载
optional: 关联是否可选。如果设置为false,则必须始终存在非空关系
@ManyToMany(多对多)
targetEntityClass: 指定另一方类的字节码
cascade: 配置级联操作
fetch: 配置延迟加载和立即加载
mappedBy: 放弃外键维护
2:配置外键关系的注解
@JoinColumn:用于定义主键字段和外键字段的对应关系。
name: 指定外键字段的名称
referencedColumnName: 指定引用主表的主键字段名称(注意是数据库字段不是实体类属性名)
unique: 是否唯一。默认值不唯一
nullable: 是否允许为空。默认值允许
insertable: 是否允许插入。默认值允许
updatable: 是否允许更新。默认值允许
columnDefinition: 列的定义信息
table
foreignKey
@JoinTable(针对中间表的设置)
name: 配置中间表的名称
joinColumns: 中间表的外键字段关联当前实体类所对应表的主键字段
@JoinColumn:
name: 本类的外键
referencedColumnName: 本类与外键(表)对应的主键
inverseJoinColumn: 中间表的外键字段关联对方表的主键字段
@JoinColumn:
name: 对方类的外键
referencedColumnName: 对方类与外键(表)对应的主键
@OrderBy(也属于关联关系注解)主要用来排序外键列集合对象
3:首先确定多表之间的关系:
一对一和一对多:
一的一方称为主表,而多的一方称为从表,外键就要建立在从表上,它们的取值的来源主要来自主键
多对多:
这个时候需要建立一个中间表,中间表中最少由2个字段组成,这2个字段作为外键指向2张表的主键又组成了联合主键
- Java持久性API(JPA)第7讲——实体生命周期及生命周期回调方法_seloba-CSDN博客
- 【原创】JPA中@PrePersist和@PreUpdate的用法_DCTANT的博客-CSDN博客_jpa prepersist
- @PreUpdate 和@PrePersist_sunrainamazing的博客-CSDN博客_prepersist
- JPA @PostPersist 等注解的使用场景和使用方法 - 码农教程 (manongjc.com)
我们在使用JPA对数据库进行操作的时候,我们时常会出现数据库字段设置未不能为空,而我们保存的字段为null导致程序报错。这个时候我们就可以使用:@PrePersist、@PostPersist 等注解回调方法来解决问题。该注释可以应用于实体类,映射超类或回调监听器类的方法。
回调方法是附加到实体生命周期事件的用户定义方法,并且在发生这些事件时由JPA自动调用。可以发现有很多类似的注解可以使用:
在Entity实体类中进行代码测试
1、创建User实体类Entity,在Entity实体内加回调方法时,回调方法形参可有可无(外部类定义时必须要有)
package entity;
import lombok.Data;
import javax.persistence.*;
@Data
@Entity
@Table(name = "sys_user")
public class User {
@Id
private Long id;
private String username;
private String password;
@PostLoad
public void postLoad(){
System.out.println("PreLoad 生命周期方法被调用! ");
}
@PrePersist
public void prePersist(){
System.out.println("PrePersist 生命周期方法被调用! ");
}
@PreUpdate
public void preUpdate(){
System.out.println("PreUpdate 生命周期方法被调用! ");
}
@PreRemove
public void preRemove(){
System.out.println("PreRemove 生命周期方法被调用! ");
}
@PostPersist
public void postPersist(){
System.out.println("PostPersist 生命周期方法被调用! ");
}
@PostUpdate
public void postUpdate(){
System.out.println("PostUpdate 生命周期方法被调用! ");
}
@PostRemove
public void postRemove(){
System.out.println("PostRemove 生命周期方法被调用! ");
}
}
2、进行CRUD单元测试
import entity.User;
import org.junit.Test;
import javax.persistence.*;
public class TestDemo {
@Test
public void testSave() {
// 1.加载配置文件创建工厂(实体管理器工厂)对象
EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
// 2.通过实体管理器工厂获取实体管理器
EntityManager em = factory.createEntityManager();
// 3.获取事务对象,开启事务
EntityTransaction tx = em.getTransaction();
// 开启事物,保存操作
tx.begin();
User user = new User();
user.setId(1L);
user.setUsername("admin");
user.setPassword("password");
em.persist(user);
tx.commit();
// 查询操作,查询同一个数据需要先清理一级缓存,不然不会发送查询SQL
em.refresh(user);
User user_select = em.find(User.class, 1L);
// 开启事物,修改操作
tx.begin();
user_select.setUsername("root");
em.merge(user_select);
tx.commit();
// 开启事物,删除操作
tx.begin();
em.remove(user_select);
tx.commit();
// 6.释放资源
em.close();
factory.close();
}
}
3、输出日志
PrePersist 生命周期方法被调用!
Hibernate:
insert
into
sys_user
(password, username, id)
values
(?, ?, ?)
PostPersist 生命周期方法被调用!
Hibernate:
select
user0_.id as id1_0_0_,
user0_.password as password2_0_0_,
user0_.username as username3_0_0_
from
sys_user user0_
where
user0_.id=?
PreLoad 生命周期方法被调用!
PreUpdate 生命周期方法被调用!
Hibernate:
update
sys_user
set
password=?,
username=?
where
id=?
PostUpdate 生命周期方法被调用!
PreRemove 生命周期方法被调用!
Hibernate:
delete
from
sys_user
where
id=?
PostRemove 生命周期方法被调用!
@EntityListeners:指定外部生命周期事件实现类,在Entity实体类中进行代码测试
1、创建User实体类(这里为了方便查看,使用static建了一个内部类,实际上登录外部新建一个类)
PS:在外部类中使用@PrePersist等注解时,必须要注意:回调方法中要有Object entity参数,不然会报错。
package entity;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
@Data
@Entity
@Table(name = "sys_user")
@EntityListeners(User.UserListener.class)
public class User implements Serializable {
@Id
private Long id;
private String username;
private String password;
public static class UserListener{
@PostLoad
public void postLoad(Object user){
System.out.println("PreLoad 生命周期方法被调用! ");
}
@PrePersist
public void prePersist(Object user){
System.out.println("PrePersist 生命周期方法被调用! ");
}
@PreUpdate
public void preUpdate(Object user){
System.out.println("PreUpdate 生命周期方法被调用! ");
}
@PreRemove
public void preRemove(Object user){
System.out.println("PreRemove 生命周期方法被调用! ");
}
@PostPersist
public void postPersist(Object user){
System.out.println("PostPersist 生命周期方法被调用! ");
}
@PostUpdate
public void postUpdate(Object user){
System.out.println("PostUpdate 生命周期方法被调用! ");
}
@PostRemove
public void postRemove(Object user){
System.out.println("PostRemove 生命周期方法被调用! ");
}
}
}
2、进行CRUD单元测试与输出日志(单元测试可以复用上面的,日志输出与上面也是一摸一样)
两种方式的区别:内部Entity时回调方法形参可有可无,但是外部实现时回调函数形参必须要有
JPA实体属性类型转换器: @Convert + AttributeConverter,通过 @Convert 注解指定自定义转换器,可用于实体属性类型与数据库字段类型之间的相互转换,便于将数据存储至数据库或从数据库读取数据。
@Repeatable(Converts.class)
@Target({METHOD, FIELD, TYPE}) @Retention(RUNTIME)
public @interface Convert {
Class converter() default void.class;
String attributeName() default "";
boolean disableConversion() default false; // 用于禁用自动应用或继承的转换器。如果为true,则不应指定converter
}
自定义转换器详解:
AttributeConverter
接口,X 为实体属性类型,Y 为数据库字段类型操作示例:
1、创建Convert,需要实现AttributeConverter接口
/**
* 该转换器主要处理遇到null转为空字符串
*/
@Converter
public class CustConvert implements AttributeConverter<String, String> {
/**
* 将实体属性x转化为y存储到数据库中,即插入和更新操作时执行
*/
@Override
public String convertToDatabaseColumn(String attribute) {
return Objects.isNull(attribute) ? "" : attribute;
}
/**
* 将数据库中的字段y转化为实体属性x,即查询操作时执行
*/
@Override
public String convertToEntityAttribute(String dbData) {
return Objects.isNull(dbData) ? "" : dbData;
}
}
2、实体类对象使用@Convert指定转换器
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "tb_convert")
public class CustConvertVO {
@Id
private Long id;
@Convert(converter = CustConvert.class)
private String username;
}
3、测试操作
public class JpaConvertTest {
/**
* 运行之前,修改hibernate.hbm2ddl.auto=create
*/
@Test
public void testSave() {
// 获取实体管理器工厂
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("myJpa");
// 获取实体管理器
EntityManager entityManager = entityManagerFactory.createEntityManager();
// 获取事务
EntityTransaction transaction = entityManager.getTransaction();
// 开启事务
transaction.begin();
// 创建实体对象并保存
entityManager.persist(new CustConvertVO(1L,null));
entityManager.persist(new CustConvertVO(2L, "sam"));
// 提交事务
transaction.commit();
entityManager.clear();
System.out.println(entityManager.find(CustConvertVO.class, 1L));
System.out.println(entityManager.find(CustConvertVO.class, 2L));
// 释放资源
entityManager.close();
entityManagerFactory.close();
}
}
4、查看日志:
Hibernate:
insert
into
tb_convert
(username, id)
values
(?, ?)
Hibernate:
insert
into
tb_convert
(username, id)
values
(?, ?)
Hibernate:
select
custconver0_.id as id1_3_0_,
custconver0_.username as username2_3_0_
from
tb_convert custconver0_
where
custconver0_.id=?
CustConvertVO(id=1, username=)
Hibernate:
select
custconver0_.id as id1_3_0_,
custconver0_.username as username2_3_0_
from
tb_convert custconver0_
where
custconver0_.id=?
CustConvertVO(id=2, username=sam)
由于Hibernate-Validation篇幅教程过于多,后面可能会单独教程笔记,暂时请参考如下教程:
- SpringBoot使用Validation校验参数(CSDN):https://blog.csdn.net/justry_deng/article/details/86571671
- SpringBoot使用Hibernate-Validator校验(博客园):https://www.cnblogs.com/mr-yang-localhost/p/7812038.html
- SpringBoot中在除Controller层 使用Validation的方式(博客园):https://www.cnblogs.com/gxc6/p/11407599.html
- Spring Boot 参数校验(CSDN-废物大师兄):https://www.cnblogs.com/cjsblog/p/8946768.html
- 使用Spring Validation优雅地校验参数(博客园):https://www.cnblogs.com/zhengxl5566/p/13398546.html