经过一番研究,我来到了下面的代码。首先,我需要创建一个自定义存储库接口和实现类,它扩展了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
字段中的一些自定义生成器,如this answer。
@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, ?, ?)
我不确定这是否是最简单的解决方案,但至少它确实是我所需要的。我正在使用Spring Boot 1.2.8。随着更新版本的实现可能会有所不同。