JPA: как обрабатывать сопоставление сущностей с двумя внешними ключами, и эти два внешних ключа действуют как первичный ключ - PullRequest
2 голосов
/ 11 января 2020

У меня есть 2 таблицы:

Таблица A

id(PK)  name
------------
1       aa
2       ab
3       ac
4       ad

Таблица B

master_id(FK_id)    slave_id(FK_id)
1                   2
2                   3
2                   4

Таблица A и B имеет отношения один ко многим. Таблица B имеет составной ключ из 2 столбцов (master_id, slave_id), как показано выше. И оба столбца в Таблице B имеют отношение внешнего ключа к таблице A.

Как мы можем обработать это сопоставление сущностей, используя пружину JPA / hibernate?

Ответы [ 2 ]

1 голос
/ 11 января 2020

Если вам нужен полный контроль над таблицей B, тогда подход Ильиных (IdClass) хорош (в качестве альтернативы вы можете использовать EmbeddedId).

Но если вам «просто» нужен «Много- Соотношение хозяин / подчиненный ко-многим между "A"

..., тогда это лучше:

@Entity
public class A implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToMany
    @JoinTable(
            name = "B",
            joinColumns = @JoinColumn(name = "slave_id"),
            inverseJoinColumns = @JoinColumn(name = "master_id"))
    private List<A> myMasters;

    @ManyToMany(mappedBy = "myMasters")
    private List<A> mySlaves;

    private String name;
 // getter, setter, hashCode, equals...
}

Тестовые данные:

insert into A (id, name) 
values (1, 'aa'),
       (2, 'ab'),
       (3, 'ac'),
       (4, 'ad');

insert into B (master_id, slave_id)
values (1, 2),
       (2, 3),
       (2, 4);

Тест:

@SpringBootTest
class ApplicationTests {

    @Autowired
    private EntityManager entityManager;

    @Test
    @Transactional
    void test() {
        assertThat(entityManager);
        A one = entityManager.find(A.class, 1L);
        assertThat(one);
        assertThat(one.getMyMasters().isEmpty());
        A two = entityManager.find(A.class, 2L);
        assertThat(two);
        assertThat(one.getMySlaves().contains(two));
        assertThat(two.getMyMasters().contains(one));
    }

}

Выход:

...
      .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.2.RELEASE)

2020-01-11 17:29:53.400  INFO 10532 --- [           main] c.example.soq59695741.ApplicationTests   : Starting ApplicationTests on ******* with PID 10532 (started by **** in D:\***\***\***)
2020-01-11 17:29:53.403  INFO 10532 --- [           main] c.example.soq59695741.ApplicationTests   : No active profile set, falling back to default profiles: default
2020-01-11 17:29:53.927  INFO 10532 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2020-01-11 17:29:53.956  INFO 10532 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 15ms. Found 0 JPA repository interfaces.
2020-01-11 17:29:54.514  INFO 10532 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-01-11 17:29:54.806  INFO 10532 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2020-01-11 17:29:54.893  INFO 10532 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-01-11 17:29:54.973  INFO 10532 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {5.4.9.Final}
2020-01-11 17:29:55.184  INFO 10532 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2020-01-11 17:29:55.606  INFO 10532 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2020-01-11 17:29:56.327  INFO 10532 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-01-11 17:29:56.335  INFO 10532 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-01-11 17:29:56.509  INFO 10532 --- [           main] c.example.soq59695741.ApplicationTests   : Started ApplicationTests in 3.437 seconds (JVM running for 4.615)
2020-01-11 17:29:56.620  INFO 10532 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@3a393455 testClass = ApplicationTests, testInstance = com.example.soq59695741.ApplicationTests@5829e4f4, testMethod = test@ApplicationTests, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@13518f37 testClass = ApplicationTests, locations = '{}', classes = '{class com.example.soq59695741.Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@7fd7a283, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@16612a51, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@47c81abf, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@6b0d80ed], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@2fa879ed]; rollback [true]
2020-01-11 17:29:56.796  INFO 10532 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@3a393455 testClass = ApplicationTests, testInstance = com.example.soq59695741.ApplicationTests@5829e4f4, testMethod = test@ApplicationTests, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@13518f37 testClass = ApplicationTests, locations = '{}', classes = '{class com.example.soq59695741.Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@7fd7a283, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@16612a51, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@47c81abf, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@6b0d80ed], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.154 s - in com.example.soq59695741.ApplicationTests
2020-01-11 17:29:56.859  INFO 10532 --- [extShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2020-01-11 17:29:56.859  INFO 10532 --- [extShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
2020-01-11 17:29:56.861 ERROR 10532 --- [extShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000478: Unsuccessful: drop table a if exists
2020-01-11 17:29:56.870  INFO 10532 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2020-01-11 17:29:56.874  INFO 10532 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Results:

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 7.976 s
0 голосов
/ 11 января 2020

Отображение для таблицы A довольно просто:

@Entity
public class A {

    @Id
    private Long id;

    private String name;

    // setters, getters, etc
}

Для таблицы B вы должны объявить отношения многие к одному и составной идентификатор. С @EmbeddedId вы не можете использовать ссылки на сущности в качестве полей, поэтому @IdClass выглядит лучше в этом случае:

@Entity
@IdClass(BId.class)
public class B {

    @Id
    @ManyToOne
    private A master;

    @Id
    @ManyToOne
    private A slave;

    // setters, getters, etc

}

И последний, BId.class. Опять же, вы не можете использовать ссылки на сущности в качестве полей, но они могут быть заменены типом A.id (Long):

public class BId implements Serializable {

    private Long master;

    private Long slave;

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...