我想知道JPA(Hibernate)是否可以通过关联所有者间接地持久化和更新实体。
我的项目中有两个数据源。我正试图找到一种在数据库之间共享某些实体的方法。为此,我只需要使用我的每个Entity Manager Factories扫描两次。根据我的想法,可以在两个数据库中使用Employee
实体。为此,我只需要在第二个数据源中创建一个Phone
实体,并将所有字段通过Hibernate迁移到我的第二个数据库。
以下是代码示例(我使用了lombok并删除了导入以简化它)
@Entity
@Table(uniqueConstraints = {
@UniqueConstraint(columnNames = {"name"})})
@lombok.NoArgsConstructor(access = PROTECTED)
@lombok.AllArgsConstructor
@lombok.Data
public class Employee {
@Id
private Long id;
private String name;
}
@Entity
@lombok.Data
@lombok.NoArgsConstructor(access = PROTECTED)
public class Phone {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne(cascade = {PERSIST, MERGE, REFRESH})
@JoinColumn(name = "em_id")
private Employee employee;
private String number;
public Phone(Employee employee, String number) {
this.employee = employee;
this.number = number;
}
}
我想使用配置为使用我的第二个数据源的Spring Data Jpa PhoneRepository
public interface PhoneRepository extends JpaRepository<Phone, Long> {}
我认为,EmployeeRepository只能使用一次配置第一个数据源。第二个数据库中的所有关系都可以由Spring / Hibernate自动创建。至少,我想这样。在我的测试中,它配置了我的第二个数据源,仅用于说明目的。
public interface EmployeeRepository extends JpaRepository<Employee, Long> {}
以下是一些测试
@Autowired
EmployeeRepository employeeRepository;
@Autowired
PhoneRepository phoneRepository;
/**
* Passes successfully.
*/
@Test
public void shouldPersitPhonesCascaded() {
phoneRepository.save(new Phone(new Employee(1L, "John Snow"), "1101"));
phoneRepository.save(new Phone(new Employee(2L, "Hans Schnee"), "1103"));
}
/**
* Causes <blockquote><pre>
* org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["PRIMARY KEY ON PUBLIC.EMPLOYEE(ID)"; SQL statement:
* insert into employee (name, id) values (?, ?) [23505-190]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
* at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
* ...
* </pre></blockquote>
*/
@Test
public void shouldMergePhonesCascaded() {
Employee employee = new Employee(1L, "John Snow");
phoneRepository.save(new Phone(employee, "1101"));
phoneRepository.save(new Phone(employee, "1102"));
}
/**
* Works with changed Phone entity's field.
* <blockquote><pre>
* {@literal @}ManyToOne
* {@literal @}JoinColumn(name = "em_id")
* private Employee employee;
* </pre></blockquote>
*/
@Test
public void shouldAllowManualMerging() {
Employee employee = new Employee(1L, "John Snow");
employeeRepository.save(employee);
phoneRepository.save(new Phone(employee, "1101"));
phoneRepository.save(new Phone(employee, "1102"));
}
理想情况下,我想从我的第一个数据源中获取一个对象(Employee
),将其放入我的第二个数据源的包装实体(Phone
)中,并更新第二个数据库而不会违反。
答案 0 :(得分:0)
经过一番研究后,我得到了以下代码。首先,我需要创建一个自定义存储库接口和实现类,它扩展SimpleJpaRepository
并完全重复SimpleJpaRepository
的功能,但Save
方法除外。
@NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {}
public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {
private final EntityManager em;
private final JpaEntityInformation<T, ?> entityInformation;
public BaseRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
this.em = entityManager;
this.entityInformation = JpaEntityInformationSupport.getMetadata(domainClass, entityManager);
}
private static void mergeFieldsRecursively(EntityManager em, Object entity) {
MergeColumns merge = entity.getClass().getDeclaredAnnotation(MergeColumns.class);
if (merge != null) {
for (String columnName : merge.value()) {
Field field = ReflectionUtils.findField(entity.getClass(), columnName);
ReflectionUtils.makeAccessible(field);
Object value = ReflectionUtils.getField(field, entity);
mergeFieldsRecursively(em, value);
em.merge(value);
}
}
}
@Transactional
@Override
public <S extends T> S save(S entity) {
mergeFieldsRecursively(em, entity);
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
}
MergeColumns
这是一个简单的注释。它描述了在持久化实体之前应合并哪些字段。
@Documented
@Target(TYPE)
@Retention(RUNTIME)
public @interface MergeColumns {
String[] value();
}
RepositoryFactoryBean
将SimpleJpaRepository
替换为自定义实施 - BaseRepositoryImpl
。
public class BaseRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> {
@Override
@SuppressWarnings("unchecked")
protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) {
return new BaseRepositoryFactory<>(em);
}
private static class BaseRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {
private final EntityManager em;
public BaseRepositoryFactory(EntityManager em) {
super(em);
this.em = em;
}
@Override
@SuppressWarnings("unchecked")
protected Object getTargetRepository(RepositoryMetadata metadata) {
return new BaseRepositoryImpl<T, I>((Class<T>) metadata.getDomainType(), em);
}
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return BaseRepositoryImpl.class;
}
}
}
...然后它应该放在第二个数据源配置
中@EnableJpaRepositories(
entityManagerFactoryRef = SECOND_ENTITY_MANAGER_FACTORY,
transactionManagerRef = SECOND_PLATFORM_TX_MANAGER,
basePackages = {"com.packages.to.scan"},
repositoryFactoryBeanClass = BaseRepositoryFactoryBean.class)
public class SecondDataSourceConfig { /*...*/ }
某些实体将从我的第一个数据源中获取。可选地,可以为id
中的@Entity
@Table(uniqueConstraints = {
@UniqueConstraint(columnNames = {"name"})})
@lombok.NoArgsConstructor(access = PROTECTED)
@lombok.AllArgsConstructor
@lombok.Data
public class Department {
@Id
private Long id;
private String name;
}
@Entity
@Table(uniqueConstraints = {
@UniqueConstraint(columnNames = {"name"})})
@MergeColumns({"department"})
@lombok.NoArgsConstructor(access = PROTECTED)
@lombok.AllArgsConstructor
@lombok.Data
public class Employee {
@Id
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "dept_id")
private Department department;
}
字段使用一些自定义生成器。
Phone
并且@Entity
@lombok.Data
@lombok.NoArgsConstructor(access = PROTECTED)
@MergeColumns({"employee"})
public class Phone {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne
@JoinColumn(name = "em_id")
private Employee employee;
private String number;
public Phone(Employee employee, String number) {
this.employee = employee;
this.number = number;
}
}
public interface PhoneRepository extends BaseRepository<Phone, Long> {}
实体“包装”它们以与我的第二个数据源一起使用
import static org.assertj.core.api.Assertions.assertThat;
...
@Autowired
PhoneRepository phoneRepository;
@Test
public void shouldPersitPhonesCascaded() {
phoneRepository.save(new Phone(new Employee(1L, "John Snow", new Department(1L, "dev")), "1101"));
phoneRepository.save(new Phone(new Employee(2L, "Hans Schnee", new Department(2L, "qa")), "1103"));
assertThat(phoneRepository.findAll()).extracting(p -> p.getEmployee().getName()).containsExactly("John Snow", "Hans Schnee");
}
@Test
public void shouldMergePhonesCascaded() {
Employee employee = new Employee(1L, "John Snow", new Department(1L, "dev"));
phoneRepository.save(new Phone(employee, "1101"));
phoneRepository.save(new Phone(employee, "1102"));
assertThat(phoneRepository.findAll()).extracting(p -> p.getEmployee().getName()).containsExactly("John Snow", "John Snow");
}
两个测试都成功通过
Hibernate: select department0_.id as id1_3_0_, department0_.name as name2_3_0_ from department department0_ where department0_.id=?
Hibernate: select employee0_.id as id1_4_0_, employee0_.dept_id as dept_id3_4_0_, employee0_.name as name2_4_0_ from employee employee0_ where employee0_.id=?
Hibernate: insert into department (name, id) values (?, ?)
Hibernate: insert into employee (dept_id, name, id) values (?, ?, ?)
Hibernate: select employee_.id, employee_.dept_id as dept_id3_4_, employee_.name as name2_4_ from employee employee_ where employee_.id=?
Hibernate: insert into phone (id, em_id, number) values (null, ?, ?)
Hibernate: select department0_.id as id1_3_0_, department0_.name as name2_3_0_ from department department0_ where department0_.id=?
Hibernate: select employee0_.id as id1_4_0_, employee0_.dept_id as dept_id3_4_0_, employee0_.name as name2_4_0_ from employee employee0_ where employee0_.id=?
Hibernate: insert into department (name, id) values (?, ?)
Hibernate: insert into employee (dept_id, name, id) values (?, ?, ?)
Hibernate: select employee_.id, employee_.dept_id as dept_id3_4_, employee_.name as name2_4_ from employee employee_ where employee_.id=?
Hibernate: insert into phone (id, em_id, number) values (null, ?, ?)
以下是 Hibernate 在我的第一次测试
中的工作原理$python3 mnist_1.0_softmax.py
我不确定这是最简单的解决方案,但至少它完全符合我的需要。我正在使用 Spring Boot 1.2.8 。对于较新的版本,实现可能会有所不同。