Java 在JPA中级联。通过关联的所有者持久化和合并实体

Java 在JPA中级联。通过关联的所有者持久化和合并实体,java,hibernate,jpa,spring-data,spring-data-jpa,Java,Hibernate,Jpa,Spring Data,Spring Data Jpa,我想知道JPA(Hibernate)是否可以通过关联所有者间接地持久化和更新实体 我的项目中有两个数据源。我试图找到一种在数据库之间共享一些实体的方法。为此,我只需要用我的每个实体管理器工厂扫描两次。根据我的想法,两个数据库中都可以使用员工实体。为此,我只需要在第二个数据源中创建一个Phone实体,所有it字段都将通过Hibernate迁移到第二个数据库中 下面是一个代码示例(我使用并删除了导入来简化它) 我想使用一个配置为与我的第二个数据源一起工作的SpringDataJPaPhoneRepo

我想知道JPA(Hibernate)是否可以通过关联所有者间接地持久化和更新实体

我的项目中有两个数据源。我试图找到一种在数据库之间共享一些实体的方法。为此,我只需要用我的每个实体管理器工厂扫描两次。根据我的想法,两个数据库中都可以使用
员工
实体。为此,我只需要在第二个数据源中创建一个
Phone
实体,所有it字段都将通过Hibernate迁移到第二个数据库中

下面是一个代码示例(我使用并删除了导入来简化它)

我想使用一个配置为与我的第二个数据源一起工作的SpringDataJPaPhoneRepository

public interface PhoneRepository extends JpaRepository<Phone, Long> {}
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, ?, ?)
public interface PhoneRepository扩展了JpaRepository{}
我认为,EmployeeRepository只能使用一次来配置第一个数据源。第二个数据库中的所有关系都可以通过Spring/Hibernate自动创建。至少,我想要这个。在我下面的测试中,它配置了第二个数据源,仅用于说明目的

public interface EmployeeRepository extends JpaRepository<Employee, Long> {}
public接口EmployeeRepository扩展了JpaRepository{}
这里有一些测试

@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"));
}
@Autowired
雇员职位雇员职位;
@自动连线
电话储存库;
/**
*成功通过。
*/
@试验
公共无效应被分类为{
phoneRepository.save(新电话(新员工(1L,“John Snow”),“1101”);
phoneRepository.save(新电话(新员工(2L,“Hans Schnee”),“1103”);
}
/**
*原因
*org.springframework.dao.DataIntegrityViolationException:无法执行语句;SQL[n/a];约束[“PUBLIC.EMPLOYEE(ID)上的主键];SQL语句:
*在员工(姓名、id)值(?,)[23505-190]]中插入;嵌套异常为org.hibernate.exception.ConstraintViolationException:无法执行语句
*位于org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
* ...
* 
*/
@试验
公共空间应合并电话卡(){
员工=新员工(1L,“约翰·斯诺”);
保存(新电话(员工,“1101”);
保存(新电话(员工,“1102”);
}
/**
*使用已更改电话实体的字段。
* 
*{@literal@}ManyToOne
*{@literal@}JoinColumn(name=“em_id”)
*私人雇员;
* 
*/
@试验
public void应允许ManualMerging(){
员工=新员工(1L,“约翰·斯诺”);
employeeRepository.save(雇员);
保存(新电话(员工,“1101”);
保存(新电话(员工,“1102”);
}

理想情况下,我希望从第一个数据源中获取一个对象(
Employee
),从第二个数据源将其放入包装实体(
Phone
),并在不违反规则的情况下更新第二个数据库。首先,我需要创建一个自定义存储库接口和实现类,该类扩展了
SimpleJpaRepository
并完全复制了
SimpleJpaRepository
的功能,除了
Save
方法之外

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;
        }
    }
}
RepositoryFactoryBean
SimpleParepository
替换为自定义实现-
BaseRepositoryImpl

@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;
}
一些实体将取自我的第一个数据源。也可以为
id
字段(如中)使用一些自定义生成器

还有一个
Phone
实体“包装”它们以用于我的第二个数据源

public interface PhoneRepository extends JpaRepository<Phone, Long> {}
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在我的第一次测试中的工作原理


我不确定这是否是最简单的解决方案,但至少它确实满足了我的需要。我用的是弹簧靴1.2.8。较新版本的实现可能有所不同。

经过一些研究,我得到了以下代码。首先,我需要创建一个自定义存储库接口和实现类,该类扩展了
SimpleJpaRepository
并完全复制了
SimpleJpaRepository
的功能,除了
Save
方法之外

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;
        }
    }
}
RepositoryFactoryBean
SimpleParepository
替换为自定义实现-
BaseRepositoryImpl

@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;
}
一些实体将取自我的第一个数据源。也可以为
id
字段(如中)使用一些自定义生成器

还有一个
Phone
实体“包装”它们以用于我的第二个数据源

public interface PhoneRepository extends JpaRepository<Phone, Long> {}
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在我的第一次测试中的工作原理

我不确定这是否是最简单的解决方案,但至少它确实满足了我的需要。我用的是弹簧靴1.2.8。对于较新版本,实现可能有所不同