jackson注解-JPA-事务处理

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控制属性的读写,功能类似@JsonIgnorePropertiesallowGetters = 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,但是是两个类

  1. @Embeddable:注解在类上,表示此类是可以被其他类嵌套
  2. @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:

  1. 只有OneToOne,OneToMany,ManyToMany上才有mappedBy属性,ManyToOne不存在该属性;
  2. mappedBy标签一定是定义在被拥有方的,他指向拥有方;
  3. mappedBy的含义,应该理解为,拥有方能够自动维护跟被拥有方的关系,当然,如果从被拥有方,通过手工强行来维护拥有方的关系也是可以做到的;
  4. mappedBy跟joinColumn/JoinTable总是处于互斥的一方,可以理解为正是由于拥有方的关联被拥有方的字段存在,拥有方才拥有了被拥有方。mappedBy这方定义JoinColumn/JoinTable总是失效的,不会建立对应的字段或者表。

mappedBy表示声明自己不是一对多的关系维护端,由对方来维护,是在一的一方进行声明的。mappedBy的值应该为一的一方的表名。

orphanRemoval:

  1. jpa 中 orphanRemoval 属性,如果为 true 的话,想要删掉子集合数据,那么调用子集合list 的 clear 方法清空,并且断关系可以直接在数据库中删除子集合数据, 不能直接设置 为null,否则抛出异常.
  2. 如果没有该属性,调用子集合list 的 clear 方法清空,并且断关系则在数据库中把 子表数据中保存的主表id 设置为空,断开关系;
  3. 而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_idrole_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