Reputation: 41
I have 2 tables:
Table A
id(PK) name
------------
1 aa
2 ab
3 ac
4 ad
Table B
master_id(FK_id) slave_id(FK_id)
1 2
2 3
2 4
Table A and B is having One-To-Many Relationship. Table B has the composite key of 2 columns (master_id, slave_id) as shown above. And both the columns in Table B has the foreign key relation with table A.
How can we handle this entity mapping using spring JPA/hibernate?
Upvotes: 2
Views: 3512
Reputation: 13261
If you need the full control of Table B, then ilinykhma's approach (IdClass
) is good (alternatively you can use EmbeddedId
).
But if you "just" need the "Many-To-Many master/slave relationship between A's"
(The nature of table B suggest so.)
..., then this is nicer:
@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...
}
Test Data:
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);
Test:
@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));
}
}
Output:
...
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: 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
Upvotes: 1
Reputation: 990
The mapping for the A table is quite simple:
@Entity
public class A {
@Id
private Long id;
private String name;
// setters, getters, etc
}
For the B table you have to declare many to one relations and the composite id. With @EmbeddedId
you can't use entity references as fields, so @IdClass
seems better in this case:
@Entity
@IdClass(BId.class)
public class B {
@Id
@ManyToOne
private A master;
@Id
@ManyToOne
private A slave;
// setters, getters, etc
}
And the last one, BId.class. Again, you can't use entity references as fields, but they can replaced by the A.id type (Long):
public class BId implements Serializable {
private Long master;
private Long slave;
}
Upvotes: 1