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<ProductEntity> 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<UserExtEntity, Long> {
}

Product DAO定义如下:

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

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<ProductEntity> 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<String> {
    @Override
    public Optional<String> 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下选择不同的数据库系统:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.bitsu.jpa</groupId>
    <artifactId>mapping</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mapping</name>
    <description>Dependency in tables.</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <profiles>
        <profile>
            <id>dev</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>

            <properties>
                <database.username>sa</database.username>
                <database.password></database.password>
                <!--
          <database.url>jdbc:h2:file:/Users/zhansan/test</database.url>
                -->
                <database.url>jdbc:h2:mem:testdb</database.url>
                <database.driver.name>org.h2.Driver</database.driver.name>
                <database.ddl.mode>update</database.ddl.mode>
                <database.platform>H2</database.platform>
                <database.dialect>org.hibernate.dialect.H2Dialect</database.dialect>
            </properties>

            <dependencies>
                <dependency>
                    <groupId>com.h2database</groupId>
                    <artifactId>h2</artifactId>
                    <scope>runtime</scope>
                </dependency>
            </dependencies>
        </profile>

        <profile>
            <id>prod</id>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <properties>
                <database.username>root</database.username>
                <database.password>123456</database.password>
                <database.url>jdbc:mysql://localhost:3306/mytest?characterEncoding=UTF-8&amp;&amp;useSSL=false&amp;&amp;serverTimezone=Asia/Shanghai</database.url>
                <database.driver.name>com.mysql.cj.jdbc.Driver</database.driver.name>
                <database.ddl.mode>update</database.ddl.mode>
                <database.platform>MYSQL</database.platform>
                <database.dialect>org.hibernate.dialect.MySQL5InnoDBDialect</database.dialect>
            </properties>
            <dependencies>
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <scope>runtime</scope>
                </dependency>
            </dependencies>
        </profile>
    </profiles>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

在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
发布了478 篇原创文章 · 获赞 803 · 访问量 433万+

猜你喜欢

转载自blog.csdn.net/blueheart20/article/details/89880780
今日推荐