Spring Boot之OneToMany、ManyToOne示例分析

Spring Boot的1对多场景

在实际使用场景中存在非常的1对多场景,对于这种情况,Spring Boot中提供基于JPA+Spring Data技术方案中,可以提供@OneToMany、@ManyToOne建立单项或者双向的依赖关系,简洁优雅地处理此类问题。

技术方案评估

基于Spring Boot框架,结合Spring Data JPA,底层使用Hibernate、Spring Data结合使用,基于ORM映射框架,来解决此类数据映射问题。
方案优点: 简洁明了,无需编写大量的代码,快捷方便
方案不足: 封装性比较高,调试有一定的复杂度,定制化开发略显复杂。

示例场景介绍

一个用户可以购买多个产品,这里的实体类有: 用户和产品类。
用户信息包括: name,location等信息。
产品信息包括: name,count,price等信息。

数据实体类定义

用户类UserEntity定义如下:

/**
 * User DAO.
 * @author  xxx
 * @date 2019-05-04
 */
@EqualsAndHashCode(callSuper = true)
@Table(name="t_user")
@Entity
@Data
@EntityListeners(AuditingEntityListener.class)
public class UserEntity extends BaseEntity {
    @Column
    private String name;

    @Column
    private String location;

    /**
     * Who owns the foreign Key, who will be the owner, and declare the JoinColumn.
     *
     */
    @JsonManagedReference
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name="user_ext_id", referencedColumnName = "id")
    private UserExtEntity userExtEntity;

    @JsonManagedReference
    @OneToMany(cascade = {CascadeType.ALL}, mappedBy = "user", fetch = FetchType.EAGER)
    private List products;
}

产品类定义如下:

/**
 * Product Entity
 *
 * @author xxx
 * @date 2019-05-04
 */
@Table(name="t_product")
@Entity
@Data
@EntityListeners(AuditingEntityListener.class)
public class ProductEntity extends BaseEntity {
    @Column
    private String name;

    @Column
    private Integer count;

    @Column
    private Float price;

    @JsonBackReference
    @ManyToOne(cascade = {CascadeType.REFRESH})
    @JoinColumn(name="user_id", referencedColumnName = "id")
    private UserEntity user;
}

在ORM的双向映射关系中存在主从两种实体,主(Host)关系,主要是指外键定义所在的Entity中的属性变量所代表的内容。在双向映射中,除了主关系之外的实例,就是从关系。
在这个示例中,从关系是UserEntity中定义的prodcuts属性
User DAO定义如下:

/**
 *  User Ext Repository
 *
 * @author  xxx
 * @date 2019-05-05
 */
@Repository
public interface UserExtRepository extends JpaRepository {
}

Product DAO定义如下:

/**
 * Product DAO.
 *
 * @author  chenjunfeng
 * @date  2019-05-04
 */
@Repository
public interface ProductRepository extends JpaRepository {
}

BaseEntity.java实体类基类定义:

@Data
@MappedSuperclass
public class BaseEntity implements java.io.Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @CreatedDate
    @Column(name="created_time")
    private Date createdTime;

    @LastModifiedDate
    @Column(name="updated_time")
    private Date updatedTime;

    @CreatedBy
    @Column(name="created_by")
    private String createdBy;

    @LastModifiedBy
    @Column(name="last_modified_by")
    private String lastModifiedBy;

    @Version
    private Integer version;
}

测试示例

测试代码定义在Controller中:

@GetMapping("/case2")
    public ResultData createUserProduct() {
       UserEntity user = new UserEntity();
       user.setLocation("TianJing");
       user.setName("WuMa");

       List products = new ArrayList<>();
       ProductEntity entity = new ProductEntity();
       entity.setCount(2);
       entity.setName("food");
       entity.setPrice(12.2f);
       entity.setUser(user);

       ProductEntity entity1 = new ProductEntity();
       entity1.setCount(2);
       entity1.setName("food");
       entity1.setPrice(12.2f);
       entity1.setUser(user);

       products.add(entity);
       products.add(entity1);

       user.setProducts(products);

       user = this.userRepo.save(user);

       ResultData resultData = ResultData.success();
       resultData.setData(user);

       return resultData;
    }

在上述示例中,每一个Product都需要设置User实例,然后基于User DAO进行数据保存,只有这样才可以将数据正确地保存到数据库中。
反之,如果在Product中未曾设置User实例,则在数据库中无法建议两者之间的关联关系。
其核心原因在于外键信息是保存在ProductEntity之中的,所以需要建立类似的映射关系。

AuditorListener

在BaseEntity中定义了所有Entity类通用的字段属性信息,其中createdTime、updatedTime、createdBy和lastModifiedBy四个字段分别使用了注解,来进行说明。
与之相对应的注解为: @CreatedDate、@LastModifiedDate、@CreatedBy和@LastModifiedBy四个注解。
这些主机都是从属于AuditorListener模块中定义的注解内容,用来监听Entity的变化以及记录其中的变化内容。
所以在Entity的定义中,需要使用声明:

@EntityListeners(AuditingEntityListener.class)

用以将相应的字段进行更新和写入。

对于Auditor特性还需要在系统层面进行启动和配置,具体配置如下:

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class PersistenceConfig {
    @Bean
    public AuditorAware auditorProvider() {
        return new AuditorAwareImpl();
    }
}

@EnableJpaAuditing用来启用Auditor特性
auditorAwareRef用来提取对应的auditor关于人的信息。这个将会单独定义:

public class AuditorAwareImpl implements AuditorAware {
    @Override
    public Optional getCurrentAuditor() {
        return Optional.of("system");
    }
}

在AuditorAwareImpl中,实现了getCurrentAuditor方法,用以给提供提供当前用户的信息。

系统配置信息

在系统层面还需要配置application.properties信息:

[email protected]@
[email protected]@
[email protected]@
[email protected]@
[email protected]@
[email protected]@
[email protected]@

在pom.xml文件中,支持在不同的profile下选择不同的数据库系统:



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.4.RELEASE
         
    
    org.bitsu.jpa
    mapping
    0.0.1-SNAPSHOT
    mapping
    Dependency in tables.

    
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter-data-jpa
        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        
        
            org.springframework.boot
            spring-boot-starter-validation
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.retry
            spring-retry
        

        
            org.springframework.boot
            spring-boot-devtools
            runtime
        
        
            org.springframework.boot
            spring-boot-configuration-processor
            true
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

    
        
            dev
            
                true
            

            
                sa
                
                
                jdbc:h2:mem:testdb
                org.h2.Driver
                update
                H2
                org.hibernate.dialect.H2Dialect
            

            
                
                    com.h2database
                    h2
                    runtime
                
            
        

        
            prod
            
                false
            
            
                root
                123456
                jdbc:mysql://localhost:3306/mytest?characterEncoding=UTF-8&&useSSL=false&&serverTimezone=Asia/Shanghai
                com.mysql.cj.jdbc.Driver
                update
                MYSQL
                org.hibernate.dialect.MySQL5InnoDBDialect
            
            
                
                    mysql
                    mysql-connector-java
                    runtime
                
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

在pom.xml中定义了两个profile:dev、prod。dev使用了H2作为默认开发数据库,在prod环境下使用mysql作为开发数据库。

Maven下的执行操作

执行dev下的Spring boot应用:

mvn spring-boot:run -Dmaven.test.skip=true -Pdev

-PprofileName: 指定profile的名称
-Dmaven.test.skip=true: 关闭自动化测试的执行
spring-boot:run 启动spring boot应用

基于IDE,目前无法很容易地切换profile,所以推荐使用命令行来操作。

MySQL配置信息

对于使用com.mysql.cj.jdbc.Driver驱动的MySQL连接信息来说,需要配置一下其serverTimeZone,具体配置如下:

jdbc:mysql://localhost:3306/mytest?characterEncoding=UTF-8&&useSSL=false&&serverTimezone=Asia/Shanghai

此为在pom.xml文件中定义的,所以&需要转化为&

& --> & amp;

关于H2数据库

在开发过程中,使用文件数据库是非常轻便和快捷的。这里同时提供了基于文件的H2数据库的配置信息。
对于H2其默认的数据库用户名/密码为sa和空。

总结

在本示例中,提供了dev、prod两种profile,分别使用不同的数据库:H2和MySQL。基于命令行来动态切换profile,并自动连接不同的数据库信息。
OneToMany、ManyToOne分别用于建立1对多的映射关系,还可以用于建立双向关联的数据关系,这些都是构建在JPA+Spring Data基础之上的。

参考资料

  1. Auditor Listener

你可能感兴趣的:(Java技术,服务化与Spring,Cloud)