Jackson注解
如果是POJO对象,有Getter和Setter方法,才能序列化和反序列化;
- @JsonInclude
@JsonInclude(JsonInclude.Include.NON_EMPTY),如果字段是空,就忽略显示。如果是数组为空,显示[]。对象为空,显示{}。这是最佳实践
- @JsonIgnore
忽略该字段,序列化和反序列化都生效
- 类注解 @JsonIgnoreProperties
@JsonIgnoreProperties(value = {“refreshTime”}, allowGetters = true)。属性refreshTime, 序列化生效,反序列化不生效。可以对多个属性统一处理。
- @JsonFormat
指定时间的格式,@JsonFormat(timezone = “GMT+8″, pattern = “yyyy/MM/dd HH:mm:ss”)可以使用在LocalDateTime
Instant
Date
对象上。
注意:序列化和反序列化都是这个格式。如果没有指定格式,则为格林尼治格式。最佳实践,为时间类型对属性指定格式。
- @JsonValue
使用此注解时,序列化当前属性的值,对象(toString),忽略其他属性。
- @JsonAlias(“myPage”)
反序列化的时候,将JSON属性myPage
映射POJO上,序列化的不受影响。
- @JsonProperty
@JsonProperty(value = “myRow”, access = JsonProperty.Access.READ_WRITE)
反序列的时候,将JSON属性myRow
映射POJO上,序列的属性值是myRow
。 这个地方是跟@JsonAlias("myRow")
的区别
access控制属性的读写,功能类似@JsonIgnoreProperties
allowGetters = true和allowSetters = true方法
- @JsonView
在controller中使用注解@JsonView(Refresh.addressDetail.class),表示「只」序列化POJO中有@JsonView(Refresh.addressDetail.class)注解的属性。对选择性序列化有用
- @JsonSerialize(using = AddressJsonSerializer.class)
指定序列化:address对象序列化成字符串
public class AddressJsonSerializer extends JsonSerializer<Address> {
@Override
public void serialize(Address address, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(address.toString());
}
}
- @JsonDeserialize(using = PhoneJsonDeserializer.class)
指定反序列化:字符串0523-09024434反序列化成phone对象
public class PhoneJsonDeserializer extends JsonDeserializer<Phone> {
@Override
public Phone deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
String[] phonetArr = node.asText().split("-");
Phone phone = new Phone();
phone.setCode(phonetArr[0]);
phone.setNumber(phonetArr[1]);
return phone;
}
}
- @JsonComponent
如果使用Jackson对JSON数据进行序列化和反序列化,则可以编写自己的JsonSerializer和JsonDeserializer。然后通过@JsonSerialize和@JsonDeSerialize来指定具体类是否使用。
同时,SpringBoot提供了一个可选的@JsonComponent注释,可以将对应的JsonSerializer和JsonDeserializer直接注入为Spring Beans,从而实现全局化处理。
- @JsonCreator
@JsonCreator
private Refresh(@JsonProperty("id") Long id) {
this.id = id;
}
当json在反序列化时,默认选择类的无参构造函数创建类对象,当没有无参构造函数时会报错,@JsonCreator作用就是指定反序列化时用的无参构造函数。构造方法的参数前面需要加上@JsonProperty,否则会报错。
- @JsonManagedReference、@JsonBackReference
jackson中的@JsonBackReference和@JsonManagedReference,以及@JsonIgnore均是为了解决对象中存在双向引用导致的无限递归(infinite recursion)问题。这些标注均可用在属性或对应的get、set方法中。
@JsonBackReference和@JsonManagedReference:这两个标注通常配对使用,通常用在父子关系中。@JsonBackReference标注的属性在序列化(serialization,即将对象转换为json数据)时,会被忽略(即结果中的json数据不包含该属性的内容)。@JsonManagedReference标注的属性则会被序列化。在序列化时,@JsonBackReference的作用相当于@JsonIgnore,此时可以没有@JsonManagedReference。但在反序列化(deserialization,即json数据转换为对象)时,如果没有@JsonManagedReference,则不会自动注入@JsonBackReference标注的属性(被忽略的父或子);如果有@JsonManagedReference,则会自动注入自动注入@JsonBackReference标注的属性。
- 代码片段
@Entity
@Table(name = "t_refresh")
@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonIgnoreProperties(value = {"refreshTime"}, allowGetters = true, allowSetters = true)
public class Refresh implements Serializable {
@JsonCreator
private Refresh(@JsonProperty("id") Long id) {
this.id = id;
}
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
// @JsonValue
private Long id;
@JsonIgnore
private String city;
/**
* 刷新,侦测时间点
* mysql: datetime类型
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy/MM/dd HH:mm:ss")
/**
* 默认就使用这个
*/
@JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime refreshTime;
/**
* @Temporal 持久化注解
* mysql: 默认time类型
*/
@JsonFormat(timezone = "GMT+8", pattern = "HH:mm:ss")
private Date createDate;
/**
* @Temporal 持久化注解
* mysql: 指定类型为TIMESTAMP
* Date + @Temporal == LocalDateTime
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy/MM/dd HH:mm:ss")
@Temporal(TemporalType.TIMESTAMP)
private Date updateDate;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy/MM/dd")
@Temporal(TemporalType.DATE)
private Date hireDate;
/**
* 页码
*/
@JsonAlias("myPage")
private Integer page;
/**
* 第几行
*/
@JsonProperty(value = "myRow", access = JsonProperty.Access.READ_ONLY)
private Integer row;
/**
* 总排名(含广告)
*/
private Integer score;
private Boolean isMatched;
private String path;
public interface addressDetail{}
public interface phoneDetail{}
@Transient
@JsonSerialize(using = AddressJsonSerializer.class)
@JsonView(addressDetail.class)
private Address address;
@Transient
@JsonDeserialize(using = PhoneJsonDeserializer.class)
@JsonView(phoneDetail.class)
private Phone phone;
public void setScore(Integer score) {
System.out.println(score);
this.score = score;
}
}
JPA注解
- Transient
默认使用字段映射, 通过@Transient设置,不用数据库字段的映射。
- @Temporal(TemporalType.TIME)
注意:@Temporal should only be set on a java.util.Date or java.util.Calendar property,不能用在LocalDateTime
上,
Date + @Temporal(TemporalType.TIMESTAMP) == LocalDateTime。最佳实践,优先使用LocalDateTime
{
"refreshTime": "2020-11-17T09:59:27", // LocalDateTime
"updateDate": "2020-11-17T15:59:27.000+0000", // Date + @Temporal(TemporalType.TIMESTAMP)
"createDate": "1970-01-02T00:30:18.000+0000", // Date,默认TemporalType.TIME
"hireDate": "2020-11-17", // Date + TemporalType.DATE
}
- @MappedSuperclass
基于代码复用和模型分离的思想,在项目开发中使用JPA的@MappedSuperclass注解将实体类的多个属性分别封装到不同的非实体类中。例如,数据库表中都需要id来表示编号,id是这些映射实体类的通用的属性,交给jpa统一生成主键id编号,那么使用一个父类来封装这些通用属性,并用@MappedSuperclas标识。
注意:
1.标注为@MappedSuperclass的类将不是一个完整的实体类,他将不会映射到数据库表,但是他的属性都将映射到其子类的数据库字段中。
2.标注为@MappedSuperclass的类不能再标注@Entity或@Table注解,也无需实现序列化接口。
- @PreUpdate、@PrePersist、@PreRemove
用于为相应的生命周期事件指定回调方法。
- @Enumerated
使用此注解映射枚举字段,以String类型存入数据库
注入数据库的类型有两种:EnumType.ORDINAL(Interger)、EnumType.STRING(String)
- @Embedded、@Embeddable
当一个实体类要在多个不同的实体类中进行使用,而其不需要生成数据库表。比如:address和user。只想生成一张表user,但是是两个类
- @Embeddable:注解在类上,表示此类是可以被其他类嵌套
- @Embedded:注解在属性上,表示嵌套被@Embeddable注解的同类型类
- @CreatedDate、@CreatedBy、@LastModifiedDate、@LastModifiedBy
表示字段为创建时间字段(insert自动设置)、创建用户字段(insert自动设置)、最后修改时间字段(update自定设置)、最后修改用户字段(update自定设置)
用法:
1、@EntityListeners(AuditingEntityListener.class):申明实体类并加注解
2、@EnableJpaAuditing:在启动类中加此注解
3、在实体类中属性中加上面四种注解
4、自定义添加用户
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
@Configuration
public class UserIDAuditorBean implements AuditorAware<Long> {
@Override
public Long getCurrentAuditor() {
SecurityContext ctx = SecurityContextHolder.getContext();
if (ctx == null) {
return null;
}
if (ctx.getAuthentication() == null) {
return null;
}
if (ctx.getAuthentication().getPrincipal() == null) {
return null;
}
Object principal = ctx.getAuthentication().getPrincipal();
if (principal.getClass().isAssignableFrom(Long.class)) {
return (Long) principal;
} else {
return null;
}
}
}
- @DynamicInsert
@DynamicInsert属性:设置为true,表示insert对象的时候,生成动态的insert语句,如果这个字段的值是null就不会加入到insert语句中,默认false。比如希望数据库插入日期或时间戳字段时,在对象字段为空定的情况下,表字段能自动填写当前的sysdate
- @DynamicUpdate
@DynamicUpdate属性:设置为true,表示update对象的时候,生成动态的update语句,如果这个字段的值是null就不会被加入到update语句中,默认false。
比如只想更新某个属性,但是却把整个属性都更改了,这并不是我们希望的结果,我们希望的结果是:我更改了哪写字段,只要更新我修改的字段就够了
- AttributeConverter
实体属性类型转换器。更多信息参考https://xhope.top/?p=1122
- 小技巧
一对多,由多的一方维护关联关系时,如公司group和部门departments。前端传递JSON,可以这么传:
{
"id": 5
"name": 'Google',
"departmentIds": [1, 2]
}
后端:
@Transient
private Long[] departmentIds;
public void setDepartmentIds(Long[] departmentIds) {
this.departmentIds = departmentIds;
// 反序列化的时候,进行处理
if (Objects.nonNull(departmentIds) && departmentIds.length > 0) {
departments = new ArrayList<>(departmentIds.length);
for (Long departmentId : departmentIds) {
departments.add(new Department(departmentId));
}
}
}
JPA关联关系
一对多@OneToMany
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OneToMany {
Class targetEntity() default void.class;
CascadeType[] cascade() default {};
FetchType fetch() default FetchType.LAZY;
String mappedBy() default "";
boolean orphanRemoval() default false;
}
用在集合属性上:如List
表示有序,Set
表示不重复
Group.java
@OneToMany(cascade = CascadeType.PERSIST, orphanRemoval= true, mappedBy = "group")
private List<Department> departments;
“一对多”或“多对一”,需要在多的一方加外键。通过注解@JoinColumn
完成
@JoinColumn(name = "group_id")
这个既可以加在“多”的一方,也可以加在“一”的一方。
1. 加在“一”的一方表示由“一”的一方维护。
2. 加在“多”的一方表示由多的一方维护。“一”多一方必须使用mappedBy
交出控制。否则可能会创建表(字段)
mappedBy:
- 只有OneToOne,OneToMany,ManyToMany上才有mappedBy属性,ManyToOne不存在该属性;
- mappedBy标签一定是定义在被拥有方的,他指向拥有方;
- mappedBy的含义,应该理解为,拥有方能够自动维护跟被拥有方的关系,当然,如果从被拥有方,通过手工强行来维护拥有方的关系也是可以做到的;
- mappedBy跟joinColumn/JoinTable总是处于互斥的一方,可以理解为正是由于拥有方的关联被拥有方的字段存在,拥有方才拥有了被拥有方。mappedBy这方定义JoinColumn/JoinTable总是失效的,不会建立对应的字段或者表。
mappedBy表示声明自己不是一对多的关系维护端,由对方来维护,是在一的一方进行声明的。mappedBy的值应该为一的一方的表名。
orphanRemoval:
- jpa 中 orphanRemoval 属性,如果为 true 的话,想要删掉子集合数据,那么调用子集合list 的 clear 方法清空,并且断关系可以直接在数据库中删除子集合数据, 不能直接设置 为null,否则抛出异常.
- 如果没有该属性,调用子集合list 的 clear 方法清空,并且断关系则在数据库中把 子表数据中保存的主表id 设置为空,断开关系;
- 而cascade 是总开关,如果 这里没有设置 CascadeType.all 或者 delete ,那么就算 orphanRemoveal 设置为 true 也无法执行删除.
FetchType:
FetchType.LAZY:懒加载,加载一个实体时,定义懒加载的属性不会马上从数据库中加载
FetchType.EAGER:急加载,加载一个实体时,定义急加载的属性会立即从数据库中加载
CascadeType
CascadeType.MERGE级联更新:若items属性修改了那么order对象保存时同时修改items里的对象。对应EntityManager的merge方法 (较常用 )
CascadeType.PERSIST级联保存:对order对象保存时也对items里的对象也会保存。对应EntityManager的presist方法
CascadeType.REFRESH级联刷新:获取order对象里也同时也重新获取最新的items时的对象。对应EntityManager的refresh(object)方法有效。即会重新查询数据库里的最新数据
CascadeType.REMOVE级联删除:对order对象删除也对items里的对象也会删除。对应EntityManager的remove方法
CascadeType.ALL包含所有;
多对一@ManyToOne
public @interface ManyToOne {
Class targetEntity() default void.class;
CascadeType[] cascade() default {};
FetchType fetch() default FetchType.EAGER;
boolean optional() default true;
}
optional
同表示是否允许为空。比如公司和部门。不允许没有公司的部门,所以需要设置optional = false
。
Group group = new Group();
group.setId(1L);
Department department = new Department();
department.setName("111");
department.setGroup(group); // @ManyToOne(optional = false) 必须设置group
departmentRepository.save(department);
一对一
多对多@ManyToMany
“多对多”通过JoinTable
增加一张中间表,跟@JoinColumn
增加一个外键字段的作用一致的,提供“连接”。
public @interface ManyToMany {
Class targetEntity() default void.class;
CascadeType[] cascade() default {};
FetchType fetch() default FetchType.LAZY;
String mappedBy() default "";
}
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "t_user_role",
joinColumns = { @JoinColumn(name = "user_id") },
inverseJoinColumns = { @JoinColumn(name = "role_id") })
public Set<Role> getRoles() {
return roles;
}
这样就会创建一张中间表t_user_role
,分别有两个字段user_id
和role_id
,分别指向user和role表。
如果想在中间表中,添加自己的「id」, 「创建时间」等。@ManyToMany就不能实现了。
可以自己通过创建中间表UserRole
User 1 <-> N UserRole
Role 1 <-> N UserRole
User.java
@Data
@Entity
@Table(name = "t_user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "user_id")
private List<UserRole> roleList;
}
Role.java
@Data
@Entity
@Table(name = "t_role")
public class Role implements Serializable {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private String name;
}
UserRole.java
@Data
@Entity
@Table(name = "t_user_role")
public class UserRole {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private LocalDateTime createdDate;
@ManyToOne
private User user;
@ManyToOne
@JoinColumn(name = "role_id")
private Role role;
}
Test.java
Role role = new Role();
role.setName("admin1");
roleRepository.save(role); // 保存角色
User user = new User();
user.setName("Rick1");
UserRole userRole = new UserRole();
userRole.setCreatedDate(LocalDateTime.now());
userRole.setRole(role); // 绑定角色关联关系
user.setRoleList(Lists.newArrayList(userRole)); // 绑定用户跟关系表
userRole.setRole(role);
userRepository.save(user);
事务处理
通过spring注解@Transactional
实现。 org.springframework.transaction.annotation.Transactional,这个注解对JPA操作和JdbcTemplate,都能统一管理事务,也就是说可以在一个service中同时使用JdbcTemplate操作,又可以使用JPA操作。
@Test
@Transactional
public void testAddTransactional() {
Department department = new Department();
department.setName("IT");
departmentRepository.save(department);
int a = 5/0; // 抛出异常并回滚
Role role = new Role();
role.setName("admin");
roleRepository.save(role);
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
- 0、如果调用方法加@Transactional,嵌套调用的方法,发生Runtime的异常,也会回滚,即使嵌套调用的方法没加@Transactional;反之,嵌套调用的方法加注解@Transactional,发生异常,但调用方法没有加注解,那么,不回滚。??TODO,需要在深入理解下“spring的事务传播行为”,如果嵌套但方法是当前对象的方法,this是当前被代理的对象,已经不是spring自动代理的对象了。之前做cache的时候,也遇到类似的情况。Spring事务管理
- 1、异常在A方法内抛出,则A方法就得加注解
- 2、多个方法嵌套调用,如果都有@Transactional 注解,则产生事务传递,默认 Propagation.REQUIRED
- 3、如果注解上只写 @Transactional 默认只对 RuntimeException 回滚,而非 Exception 进行回滚
- 如果要对 checked Exceptions 进行回滚,则需要 @Transactional(rollbackFor = Exception.class)
更多参考
* https://www.cnblogs.com/taven/p/5942384.html
* https://www.cnblogs.com/flydean/p/12680284.html