在实际使用场景中存在非常的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之中的,所以需要建立类似的映射关系。
在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作为开发数据库。
执行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,所以推荐使用命令行来操作。
对于使用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其默认的数据库用户名/密码为sa和空。
在本示例中,提供了dev、prod两种profile,分别使用不同的数据库:H2和MySQL。基于命令行来动态切换profile,并自动连接不同的数据库信息。
OneToMany、ManyToOne分别用于建立1对多的映射关系,还可以用于建立双向关联的数据关系,这些都是构建在JPA+Spring Data基础之上的。