jpa基础
# 一、jpa介绍
# SpringData简介
spirng data jpa是spring提供的一套简化JPA开发的框架,按照约定好的【方法命名规则】写dao层接口,就可以在不写接口实现的情况下,实现对数据库的访问和操作。同时提供了很多除了CRUD之外的功能,如分页、排序、复杂查询等等。
Spring Data JPA 可以理解为 JPA 规范的再次封装抽象,底层还是使用了 Hibernate 的 JPA 技术实现。
# 二、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
2
ddl-auto 几种属性
- create: 每次运行程序时,都会重新创建表,故而数据会丢失
- create-drop: 每次运行程序时会先创建表结构,然后待程序结束时清空表
- upadte: 每次运行程序,没有表时会创建表,如果对象发生改变会更新表结构,原有数据不会清空,只会更新(推荐使用)
- validate: 运行程序会校验数据与数据库的字段类型是否相同,字段不同会报错
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/security-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
username: root
password: 123456
2
3
4
5
6
# entity
/**
* @author Zheng Jie
* @date 2018-11-22
*/
@Entity
@Getter @Setter
@Table(name="sys_user")
public class User extends BaseEntity implements Serializable {
@Id
@Column(name = "user_id")
@NotNull(groups = Update.class)
@GeneratedValue(strategy = GenerationType.IDENTITY)
@ApiModelProperty(value = "ID", hidden = true)
private Long id;
@ManyToMany
@ApiModelProperty(value = "用户角色")
@JoinTable(name = "sys_users_roles",
joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "role_id")})
private Set<Role> roles;
@ManyToMany
@ApiModelProperty(value = "用户岗位")
@JoinTable(name = "sys_users_jobs",
joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "job_id",referencedColumnName = "job_id")})
private Set<Job> jobs;
@OneToOne
@JoinColumn(name = "dept_id")
@ApiModelProperty(value = "用户部门")
private Dept dept;
@NotBlank
@Column(unique = true)
@ApiModelProperty(value = "用户名称")
private String username;
@NotBlank
@ApiModelProperty(value = "用户昵称")
private String nickName;
@Email
@NotBlank
@ApiModelProperty(value = "邮箱")
private String email;
@NotBlank
@ApiModelProperty(value = "电话号码")
private String phone;
@ApiModelProperty(value = "用户性别")
private String gender;
@ApiModelProperty(value = "头像真实名称",hidden = true)
private String avatarName;
@ApiModelProperty(value = "头像存储的路径", hidden = true)
private String avatarPath;
@ApiModelProperty(value = "密码")
private String password;
@NotNull
@ApiModelProperty(value = "是否启用")
private Boolean enabled;
@ApiModelProperty(value = "是否为admin账号", hidden = true)
private Boolean isAdmin = false;
@Column(name = "pwd_reset_time")
@ApiModelProperty(value = "最后修改密码的时间", hidden = true)
private Date pwdResetTime;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
User user = (User) o;
return Objects.equals(id, user.id) &&
Objects.equals(username, user.username);
}
@Override
public int hashCode() {
return Objects.hash(id, username);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
常见的几种自增策略
- TABLE: 使用一个特定的数据库表格来保存主键
- SEQUENCE: 根据底层数据库的序列来生成主键,条件是数据库支持序列。这个值要与generator一起使用,generator 指定生成主键使用的生成器(可能是orcale中自己编写的序列)。
- IDENTITY: 主键由数据库自动生成(主要是支持自动增长的数据库,如mysql)
- AUTO: 主键由程序控制,也是GenerationType的默认值。
# repository
创建UserRepository
数据访问层接口,需要继承JpaRepository<T,K>
,第一个泛型参数是实体对象的名称,第二个是主键类型。只需要这样简单的配置,该UserRepository
就拥常用的CRUD
功能,JpaRepository
本身就包含了常用功能,剩下的查询我们按照规范写接口即可,**JPA支持@Query
注解写HQL,也支持findAllByUsername
这种根据字段名命名的方式
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
/**
* 根据用户名查询
* @param username 用户名
* @return /
*/
User findByUsername(String username);
/**
* 根据邮箱查询
* @param email 邮箱
* @return /
*/
User findByEmail(String email);
/**
* 根据手机号查询
* @param phone 手机号
* @return /
*/
User findByPhone(String phone);
/**
* 修改密码
* @param username 用户名
* @param pass 密码
* @param lastPasswordResetTime /
*/
@Modifying
@Query(value = "update sys_user set password = ?2 , pwd_reset_time = ?3 where username = ?1",nativeQuery = true)
void updatePass(String username, String pass, Date lastPasswordResetTime);
/**
* 修改邮箱
* @param username 用户名
* @param email 邮箱
*/
@Modifying
@Query(value = "update sys_user set email = ?2 where username = ?1",nativeQuery = true)
void updateEmail(String username, String email);
/**
* 根据角色查询用户
* @param roleId /
* @return /
*/
@Query(value = "SELECT u.* FROM sys_user u, sys_users_roles r WHERE" +
" u.user_id = r.user_id AND r.role_id = ?1", nativeQuery = true)
List<User> findByRoleId(Long roleId);
/**
* 根据角色中的部门查询
* @param deptId /
* @return /
*/
@Query(value = "SELECT u.* FROM sys_user u, sys_users_roles r, sys_roles_depts d WHERE " +
"u.user_id = r.user_id AND r.role_id = d.role_id AND d.dept_id = ?1 group by u.user_id", nativeQuery = true)
List<User> findByRoleDeptId(Long deptId);
/**
* 根据菜单查询
* @param id 菜单ID
* @return /
*/
@Query(value = "SELECT u.* FROM sys_user u, sys_users_roles ur, sys_roles_menus rm WHERE\n" +
"u.user_id = ur.user_id AND ur.role_id = rm.role_id AND rm.menu_id = ?1 group by u.user_id", nativeQuery = true)
List<User> findByMenuId(Long id);
/**
* 根据Id删除
* @param ids /
*/
void deleteAllByIdIn(Set<Long> ids);
/**
* 根据岗位查询
* @param ids /
* @return /
*/
@Query(value = "SELECT count(1) FROM sys_user u, sys_users_jobs j WHERE u.user_id = j.user_id AND j.job_id IN ?1", nativeQuery = true)
int countByJobs(Set<Long> ids);
/**
* 根据部门查询
* @param deptIds /
* @return /
*/
@Query(value = "SELECT count(1) FROM sys_user u WHERE u.dept_id IN ?1", nativeQuery = true)
int countByDepts(Set<Long> deptIds);
/**
* 根据角色查询
* @param ids /
* @return /
*/
@Query(value = "SELECT count(1) FROM sys_user u, sys_users_roles r WHERE " +
"u.user_id = r.user_id AND r.role_id in ?1", nativeQuery = true)
int countByRoles(Set<Long> ids);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# Hql示例
要想查询全部字段可以用 sellect 实体名 这里省略了value ,参数使用了占位置符 ?1 代表第一个参数 ?2代表第二个。原生sql语句可以直接放到数据库中执行 nativeQuery=true
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}
//如果是更新或者删除操作,方法上面要加@Modifying 默认开启的事务只是可读的,更新操作加入@Modifying 就会关闭可读
@Modifying
@Transactional
@Query("update CardConfig set cardStatus=?1 where id in ?2")
void updateCardStatus( Integer status,List<Integer> listIds);
// @Param 代替参数占位符, hql或者sql里就用 :firstname替换 方法里的参数顺序可以打乱
@Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
User findByLastnameOrFirstname(@Param("lastname") String lastname,
@Param("firstname") String firstname);
//返回字段 组成新的entity返回 类名必须是全写的
@Query(value="select new com.hikvision.metro.modules.repository.entity.CameraIndexs(c.preOneCameraIndexcode, c.preTwoCameraIndexcode, c.backOneCameraIndexcode) from StationDeviceConfig c")
List<CameraIndexs> getAllCameraIndexs();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 审计:默认时间、创建人
@Getter
@Setter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(value={"hibernateLazyInitializer","handler","fieldHandler"})
public class BaseEntity implements Serializable {
@CreatedBy
@Column(name = "create_by", updatable = false)
@ApiModelProperty(value = "创建人", hidden = true)
private String createBy;
@LastModifiedBy
@Column(name = "update_by")
@ApiModelProperty(value = "更新人", hidden = true)
private String updateBy;
@CreationTimestamp
@Column(name = "create_time", updatable = false)
@ApiModelProperty(value = "创建时间", hidden = true)
private Timestamp createTime;
@UpdateTimestamp
@Column(name = "update_time")
@ApiModelProperty(value = "更新时间", hidden = true)
private Timestamp updateTime;
/* 分组校验 */
public @interface Create {}
/* 分组校验 */
public @interface Update {}
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this);
Field[] fields = this.getClass().getDeclaredFields();
try {
for (Field f : fields) {
f.setAccessible(true);
builder.append(f.getName(), f.get(this)).append("\n");
}
} catch (Exception e) {
builder.append("toString builder encounter an error");
}
return builder.toString();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
参考:Java JPA设置默认值、Timestamp设置、自动获取时间_gmHappy-CSDN博客_jpa timestamp (opens new window)
# JPA注解
# jpa查询
# 枚举映射
数据库枚举类型映射
解决JPA的枚举局限性 - JPA映射枚举的最佳实现 - xiaoqhuang - 博客园 (cnblogs.com) (opens new window)
# 自定义simple
# 雪花算法分布式主键生成
# 1、雪花算法工具类
package com.zxp.cloud.core.db.idgen;
import java.util.Date;
import java.util.Random;
/**
* from https://github.com/twitter/snowflake/blob/master/src/main/scala/com/twitter/service/snowflake/IdWorker.scala
* 参考文章:
* https://www.cnblogs.com/relucent/p/4955340.html
*
* Twitter_Snowflake<br>
* SnowFlake的结构如下(每部分用-分开):<br>
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
* 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
* 加起来刚好64位,为一个Long型。<br>
* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
*
*/
public class IdWorker {
/** 工作机器ID(0~31) */
private final long workerId;
/** 数据中心ID(0~31) */
private final long datacenterId;
/** 开始时间截 (2018-08-11) */
private final long idepoch;
/** 机器id所占的位数 */
private static final long workerIdBits = 5L;
/** 数据标识id所占的位数 */
private static final long datacenterIdBits = 5L;
/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
private static final long maxWorkerId = ~(-1L << workerIdBits);
/** 支持的最大数据标识id,结果是31 */
private static final long maxDatacenterId = ~(-1L << datacenterIdBits);
/** 序列在id中占的位数 */
private static final long sequenceBits = 12L;
/** 机器ID向左移12位 */
private static final long workerIdShift = sequenceBits;
/** 数据标识id向左移17位(12+5) */
private static final long datacenterIdShift = sequenceBits + workerIdBits;
/** 时间截向左移22位(5+5+12) */
private static final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
private static final long sequenceMask = ~(-1L << sequenceBits);
/** 上次生成ID的时间截 */
private long lastTimestamp = -1L;
/** 毫秒内序列(0~4095) */
private long sequence;
private static final Random r = new Random();
public IdWorker() {
this(1533971179286L);
}
public IdWorker(long idepoch) {
this(r.nextInt((int) maxWorkerId), r.nextInt((int) maxDatacenterId), 0, idepoch);
}
public IdWorker(long workerId, long datacenterId, long sequence) {
this(workerId, datacenterId, sequence, 1344322705519L);
}
//
public IdWorker(long workerId, long datacenterId, long sequence, long idepoch) {
this.workerId = workerId;
this.datacenterId = datacenterId;
this.sequence = sequence;
this.idepoch = idepoch;
if (workerId < 0 || workerId > maxWorkerId) {
throw new IllegalArgumentException("workerId is illegal: " + workerId);
}
if (datacenterId < 0 || datacenterId > maxDatacenterId) {
throw new IllegalArgumentException("datacenterId is illegal: " + workerId);
}
if (idepoch >= System.currentTimeMillis()) {
throw new IllegalArgumentException("idepoch is illegal: " + idepoch);
}
}
public long getDatacenterId() {
return datacenterId;
}
public long getWorkerId() {
return workerId;
}
public long getTime() {
return System.currentTimeMillis();
}
public long getId() {
return nextId();
}
private synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new IllegalStateException("Clock moved backwards.");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}
lastTimestamp = timestamp;
return ((timestamp - idepoch) << timestampLeftShift)//
| (datacenterId << datacenterIdShift)//
| (workerId << workerIdShift)//
| sequence;
}
/**
* get the timestamp (millis second) of id
* @param id the nextId
* @return the timestamp of id
*/
public long getIdTimestamp(long id){
return idepoch + (id >> timestampLeftShift);
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
@Override
public String toString() {
String sb = "IdWorker{" + "workerId=" + workerId +
", datacenterId=" + datacenterId +
", idepoch=" + idepoch +
", lastTimestamp=" + lastTimestamp +
", sequence=" + sequence +
'}';
return sb;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# 2、定义生成策略
package com.zxp.cloud.core.db.idgen;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
import java.io.Serializable;
/**
* snowflake主键生成器
* @Id
@GenericGenerator(name="snowflake",strategy = SnowflakeGenerator.TYPE)
@GeneratedValue(generator = "snowflake")
private PK id;
* @author shollin
* @date 2018年8月11日
*
*/
public class SnowflakeGenerator implements IdentifierGenerator {
public static final String TYPE = "com.zxp.cloud.core.db.idgen.SnowflakeGenerator";
private static final IdWorker idWorker = new IdWorker();
@Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
return idWorker.getId();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 3、主键上添加自定义策略
@Id
@GenericGenerator(name="snowflake",strategy = SnowflakeGenerator.TYPE)
@GeneratedValue(generator = "snowflake")
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
2
3
4
5
参考文章 :
Snowflake雪花算法 (opens new window)
Twitter的分布式自增ID算法snowflake (Java版) - relucent - 博客园 (cnblogs.com) (opens new window)
# jpa与lombok
使用JPA和Lombok时,请记住以下规则:
- 使用JPA实体时避免使用lombok的@EqualsAndHashCode和@Data;
- 使用@ToString时始终排除惰性lazy属性;
- 别忘了用@Builder和@AllArgsConstructor将@NoArgsConstructor添加到实体。
# 常见错误
# 序列化问题
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.zxp.springboot.jpademo.model.User$HibernateProxy$FUp3zXBu["hibernateLazyInitializer"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.12.3.jar:2.12.3]
2
原因分析: hibernate查询出来的对象,会放在
$HibernateProxy$
代理对象中,jackson在对hibernate的持久化实体类的代理对象进行序列化时,代理类中的"hibernateLazyInitializer"属性为空,触发了系列化规划SerializationFeature.FAIL_ON_EMPTY_BEANS
,即“出现空Bean时触发序列化失败”!"hibernateLazyInitializer"属性为空之所以为空是因为我们禁用了延迟加载(open-in-view: true),设为false又会引发no Session错误。
解决办法: 修改jacson的序列化机制; 或者修改bean的序列化忽略属性
@Entity
@Table(name = "tb_user")
@JsonIgnoreProperties(value = {"hibernateLazyInitializer"})
public class User {
}
2
3
4
5
/**
* 参考: org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
* @author: shollin
* @date: 2021/6/27/027 18:30
*/
@Configuration
public class JacksonConfig {
/**
* 修复 hibernateLazyInitializer序列化值有空的问题、 jdk8时间日期序列化问题
* @return
*/
@Bean
public ObjectMapper objectMapper() {
JavaTimeModule javaTimeModule = new JavaTimeModule();
/** 序列化配置,针对java8 时间 **/
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
/** 反序列化配置,针对java8 时间 **/
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
return Jackson2ObjectMapperBuilder.json().createXmlMapper(false).modules(javaTimeModule).build()
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
参考文章:
SpringBoot集成jpa 一篇就够了 超详细_cmx1060220219的博客-CSDN博客 (opens new window)