ManyToManyToMany - Joining three tables with Hibernate annotations - java

At first I thought this solution might solve my problem:
#Entity
public class User {
#JoinTable(name = "user_permission",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "permission_id"))
#MapKeyJoinColumn(name = "project_id")
#ElementCollection
private Map<Project, Permission> permissions = new HashMap<>();
}
#Entity
public class Project {
...
}
#Entity
public class Permission {
...
}
But in this implementation there can only be one Permission set per Project. I'd like to accomplish the ability to set multiple permissions for a project such that the following could be true:
| user_id | project_id | permission_id |
|---------|------------|---------------|
| 1 | 1 | 1 |
|---------|------------|---------------|
| 1 | 1 | 2 |
|---------|------------|---------------|
| 1 | 2 | 1 |
|---------|------------|---------------|
| 1 | 2 | 2 |
|---------|------------|---------------|
| 2 | 1 | 1 |
|---------|------------|---------------|
| 2 | 1 | 2 |
|---------|------------|---------------|
| 2 | 2 | 1 |
|---------|------------|---------------|
| 2 | 2 | 2 |

You can use an entity dedicated to your relation table. It's the way we declare relations with their own attributes for instance.
This would result in the following implementation:
#Entity
#IdClass(PermissionAssignation.class)
public class PermissionAssignation {
#Id
#ManyToOne
#JoinColumn(name="user_id")
private User user;
#Id
#ManyToOne
#JoinColumn(name="project_id")
private Project project;
#Id
#ManyToOne
#JoinColumn(name="permission_id")
private Permission permission;
...
}
I used the solution found in this post: Hibernate and no PK
It explains how to create the PK with field (I did not test it).
If it does not work, you'd better use a EmbeddedId class.
And if you want your relation to be bidirectional, you can then use a Set<PermissionAssignation> (or List, as you prefer/need):
#Entity
public class User {
#OneToMany(mappedBy="user")
private Set<PermissionAssignation> permissions;
}

Since I recently ran into this and still struggled, I wanted to share a complete code example. This example uses a separate #EmbeddedId class which will still create a table with 3 PK/FK columns. My example makes use of Lombok to fill in a bunch of boiler-plate code such as getters/setters, constructors, etc. It was also necessary to override the equals and hashcode methods. This was written using Spring framework, which wires up the repos & tests. Hopefully someone finds this a useful guide.
/* ENTITY CLASSES */
#Entity
#Data
#Table(name = "_Who")
public class Who {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(mappedBy = "who", fetch = FetchType.EAGER)
#JsonManagedReference
List<WhoWhatWhere> storage;
}
#Entity
#Data
#Table(name = "_What")
public class What {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String thing;
}
#Entity
#Data
#Table(name = "_Where")
public class Where {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String place;
}
#Data
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Entity
#NoArgsConstructor
#Table(name = "_WhoWhatWhere")
public class WhoWhatWhere {
public WhoWhatWhere(Who who, What what, Where where) {
this.who = who;
this.what = what;
this.where = where;
this.setId(new WhoWhatWhereId(who.getId(), what.getId(), where.getId()));
}
#EmbeddedId
WhoWhatWhereId id;
#ManyToOne(fetch = FetchType.EAGER)
#JsonBackReference
#JoinColumn(name = "who_id", insertable = false, updatable = false)
private Who who;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "what_id", insertable = false, updatable = false)
private What what;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "where_id", insertable = false, updatable = false)
private Where where;
}
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class WhoWhatWhereId implements Serializable {
#Column(name = "who_id")
Long whoId;
#Column(name = "what_id")
Long whatId;
#Column(name = "where_id")
Long whereId;
}
/* REPOSITORIES */
#Repository
public interface WhoRepository extends PagingAndSortingRepository<Who, Long> {
Iterable<Who> findWhoByName (String name);
}
#Repository
public interface WhatRepository extends PagingAndSortingRepository<What, Long> {
}
#Repository
public interface WhereRepository extends PagingAndSortingRepository<Where, Long> {
}
#Repository
public interface WhoWhatWhereRepository extends PagingAndSortingRepository<WhoWhatWhere, WhoWhatWhereId> {
}
/* TEST CLASS */
#SpringBootTest
#Slf4j
public class ThreeWayAssocTest {
private final WhoRepository whoRepository;
private final WhatRepository whatRepository;
private final WhereRepository whereRepository;
private final WhoWhatWhereRepository whoWhatWhereRepository;
#Autowired
public ThreeWayAssocTest(WhoRepository whoRepository, WhatRepository whatRepository, WhereRepository whereRepository, WhoWhatWhereRepository whoWhatWhereRepository) {
this.whoRepository = whoRepository;
this.whatRepository = whatRepository;
this.whereRepository = whereRepository;
this.whoWhatWhereRepository = whoWhatWhereRepository;
}
#Test
public void attemptPersistence() {
/*
* the commented pieces can be used to do the initial inserts. Later, fetch existing values so as not to fill
* up the database
*/
Who who =
/* new Who();
who.setName("Carl");
whoRepository.save(who);*/
whoRepository.findById(1L).get();
What what =
/* new What();
what.setThing("strawberry");
whatRepository.save(what);
what.setThing("salad");
whatRepository.save(what);*/
whatRepository.findById(2L).get();
Where where =
/* new Where();
where.setPlace("plate");
whereRepository.save(where);*/
whereRepository.findById(1L).get();
WhoWhatWhere whoWhatWhere = new WhoWhatWhere(who, what, where);
whoWhatWhereRepository.save(whoWhatWhere);
LOGGER.debug("finished");
}
#Test
public void testSerializing() throws JsonProcessingException {
Iterable<Who> examples = whoRepository.findWhoByName("Carl");
Who carl = examples.iterator().next();
LOGGER.debug("Carl: {}", carl);
LOGGER.debug("found some: \n {}", new ObjectMapper().writeValueAsString(examples));
}
}

Related

JPA #OneToOne Mapping a relationship with ReadOnly Oracle data without primary key

Preamble An Oracle DB read-only(I don't have access) has the following two tables:
person
person table
| id | name | gender |
| -- | ------ | ------ |
| 2001 | Moses | M |
| 2002 | Luke | M |
| 2003 | Maryam | F |
PK(id)
reference
reference table
| sep | guid | table_name |
| --- | -------- | ---------- |
| 2001 | EA48-... | person |
| 2002 | 047F-... | person |
| 2003 | B23F-... | person |
| 2003 | 3E3H-... | address |
| 2001 | H2E0-... | address |
| 2001 | 92E4-... | report |
No PK, it is generated by some triggers
The person table is a straight forward table with a primary key. The reference table are generated via a trigger that stores the id(PK) in sep column of any table and the table name that is store in table_name column (Note: Since no primary key, the reference table stores duplicate values in the sep column but distinct value into guid.)
Requirement
I need to use JPA to get the record from the reference table and map to the person record (person.id and other table.id are stored in reference.sep column) using Jackson as follows
{
"id": 2001,
"name": "Moses",
"gender": "M",
"reference": {
"sep": 2001,
"guid": "EA48-...",
"tableName": "person"
}
}
Entity (Person)
#Entity
#Table(name="person")
public class Person implements Serializable {
#Id
private Long id;
private String name;
private String gender;
#OneToOne
#JoinColumn(name = "id", referencedColumnName = "sep", insertable = false, updatable = false)
private Reference reference;
// Getters & Setters
}
Entity (Reference)
#Entity
#Table(name="reference")
public class Reference implements Serializable {
private Long sep;
private String guid;
private String tableName;
//Getters & Setters
}
Problem 1
JPA throws error of no #Id annotation on Reference table.
Problem 2
If I add the #Id annotation on the sep field, JPA throws error of duplicate values for that column.
Problem 3
If I add the #Id annotation on the guid field (it is unique field), JPA throws error of mapping a Long to a String field (org.hibernate.TypeMismatchException: Provided id of the wrong type for class)
Question
How can I structure the entities (Person.java and Reference.java) in order to come up with the output below:
{
"id": 2001,
"name": "Moses",
"gender": "M",
"reference": {
"sep": 2001,
"guid": "EA48-...",
"tableName": "person"
}
}
Reference is the owner of the relationship and needs to be specified as such in either a unidirectional or bidirectional relationship
// Unidirection relationship
#Entity
public class Person implements Serializable {
#Id
private Long id;
private String name;
private String gender;
// Getters & Setters
}
#Entity
public class Reference implements Serializable {
#Id
private String guid;
private String tableName;
#OneToOne
#JoinColumn(name = "sep", insertable = false, updatable = false)
private Person person;
//Getters & Setters
}
// Bidirection relationship
#Entity
public class Person implements Serializable {
#Id
private Long id;
private String name;
private String gender;
#OneToOne(mappedBy = "person")
private Reference reference;
// Getters & Setters
}
#Entity
public class Reference implements Serializable {
#Id
private String guid;
private String tableName;
#OneToOne
#JoinColumn(name = "sep", insertable = false, updatable = false)
private Person person;
//Getters & Setters
}
Same example for read any kind records from table reference:
#Entity
#Table(name = "reference")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "table_name")
public abstract class AbstractReferenceEntity {
#Id
private UUID guid;
public UUID getGuid() {
return guid;
}
public void setGuid(UUID guid) {
this.guid = guid;
}
}
#Entity
#DiscriminatorValue("person")
public class PersonReferenceEntity extends AbstractReferenceEntity {
#OneToOne
#JoinColumn(name = "sep")
private Person person;
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
// Read all types of records.
AbstractReferenceEntity e = this.em.find(AbstractReferenceEntity.class, sameUuid));
// Read only person type of records.
AbstractReferenceEntity e = this.em.find(PersonReferenceEntity, sameUuid);
For the benefit of anyone looking to solve this kind of issue, I will be posting the solution that works for me following #XtremeBaumer suggestion in the comment.
Step 1: For the REFERENCE table, I made the JPA entity to have two ids (sep & table_name) by creating an extra composite Id class and using it in the Reference Entity.
public class RefId {
private Long sep;
private String tableName;
//All args constructor
//No args constructor
//Setters & Getters
//Override the equals() and hashCode() !very important
}
Step 2: Add the above class as a composite id to the Reference entity by using the #IdClass annotation. We must also declare and annotate the two fields with #Id in the Reference class.
#Entity
#Table(name="reference")
#IdClass(RefId.class) // Important if not using embeddable type
public class Reference implements Serializable {
#Id
private Long sep;
private String guid;
#Id
private String tableName;
//Getters & Setters
}
Step 3: In the Person entity, declare #OneToOne on the Reference entity and annotate it with #JoinColumnsOrFormulas as shown below:
#Entity
#Table(name="person")
public class Person implements Serializable {
#Id
private Long id;
private String name;
private String gender;
#OneToOne
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(column = #JoinColumn(name = "id", referencedColumnName = "sep", insertable = false, updatable = false)),
#JoinColumnOrFormula(formula = #JoinFormula(value = "'person'", referencedColumnName = "tableName"))
})
private Reference reference;
// Getters & Setters
}
This works fine in the scenario above. Note in the formula = #JoinFormula, it is like we are declaring the 'WHERE' clause i.e. WHERE table_name = 'person' (Don't miss the single quotes)
Lastly, by using the Jackson object mapper, I was able to get
{
"id": 2001,
"name": "Moses",
"gender": "M",
"reference": {
"sep": 2001,
"guid": "EA48-...",
"tableName": "person"
}
}
Thanks for your insight (#XtremeBaumer)

hibernate #onetomany bidirectinal mapping doesn't map data correctly

I have two entity classes.User and FriendStatus.Friend status keeps data about friend requests that have come from another users.
User:
#Entity
#Table(name = "USERS")
#XmlRootElement
public class User implements Serializable {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "acceptor")
#LazyCollection(LazyCollectionOption.FALSE)
private List<Message> acceptedMessages;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "sender")
#LazyCollection(LazyCollectionOption.FALSE)
private List<Message> sentMessages;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
#Column(name = "phone_number")
private String phoneNumber;
#OneToMany(cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.FALSE)
private List<User> friends;
#OneToMany(cascade = CascadeType.ALL,mappedBy="requestAcceptor")
#LazyCollection(LazyCollectionOption.FALSE)
#JsonIgnoreProperties("requestAcceptor")
private List<FriendStatus> acceptedFriendRequests;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "requestSender")
#LazyCollection(LazyCollectionOption.FALSE)
#JsonIgnoreProperties("requestSender")
private List<FriendStatus> sentFriendRequests;
#Column(name = "profile_status")
private String profileStatus;
#Enumerated(EnumType.STRING)
#Column(name = "activation_status")
private UserActivationStatus activationStatus;
FriendStatus:
#Entity
#Table(name="FRIEND_STATUS")
public class FriendStatus implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long Id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="request_sender_id")
#JsonIgnoreProperties("sentFriendRequests")
private User requestSender;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="request_acceptor_id")
#JsonIgnoreProperties("acceptedFriendRequests")
private User requestAcceptor;
#Enumerated(EnumType.STRING)
#Column(name = "request_status")
private FriendRequestStatus friendRequestStatus;
When i first time save FriendStatus object to the database it works fine.But when i save object second time with the same requestAcceptor object,hibernate deletes previous id from request_acceptor_id column and writes it to the new row.HELP ME PLEASE.
EDIT:
This is the method which i save my object to db.
public T create(T object) {
T objectFromDB = object;
Session session = NewHibernateUtil.getSessionFactory().openSession();
Transaction transaction = null;
try {
transaction = (Transaction) session.beginTransaction();
session.save(object);
transaction.commit();
} catch (HibernateException e) {
if (session != null){
session.getTransaction().rollback();
}
e.printStackTrace();
} finally {
session.close();
}
return objectFromDB;
}
This is the method where i call create method:
public void sendFriendRequest(FriendStatus object) {
FriendStatus status = fDao.create(object);//fDao is the object from Dao class which includes create method.
}
This is my controller:
#RequestMapping(value="/sendFriendRequest",method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON,produces = MediaType.APPLICATION_JSON)
public String sendFriendRequest(#RequestBody FriendStatus status) {
serviceUser.sendFriendRequest(status);//serviceUser is the object from class that includes sendFriendRequest method.
return "OK";
}
This is my table in db:
+====+================+=====================+===================+
| id | request_status | request_acceptor_id | request_sender_id |
+====+================+=====================+===================+
| 18 | WAITING | NULL | 29 |
+----+----------------+---------------------+-------------------+
| 19 | WAITING | 23 | 30 |
+----+----------------+---------------------+-------------------+
When i save FriendStatus object(it comes from client) with requestSender which id is 29 and requestAcceptor object which id is 23 hibernate saves it to the column which id is 18.After that when i save second FriendStatus object with requestSender which id is 30 and requestAcceptor object which id is 23,hibernate replaces request_acceptor_id with the NULL in the row which id is 18 and then creates new row in db which request_acceptor_id is 23.But i want that when i add second object,first object don't change.I don't want to replace request_acceptor_id with NULL when i create new column with the same request_acceptor_id.
Its to do with your cascade setting,
try using the following to save:
public void sendFriendRequest(FriendStatus status) {
User requester=userDao.findOne(status.getRequestAcceptor.getId());
User sender=userDao.findOne(status.getRequestSender.getId());
......
statusDao.create(status);
status.getRequestAcceptor(requester);
status.getRequestSender(sender);
statusDao.update(status);
}
the users that status is carrying have private List sentFriendRequests and private List acceptedFriendRequests; set to empty lists
Ofcause it works out even better if your transaction is outside the whole function then you can use:
sender.getSentFriendRequests().add(status) and reqester.getacceptedFriendRequests().add(status); which i guess was the point of the #OneToMany, Cascade.All
otherwise you can just remove the cascade rules from the collections

JPA Hibernate: How to limit fetch depth for a self referencing entity

I will write an example just to reduce the complexity:
Human.java
#Entity
#Table(name = "human")
#DynamicInsert(value = true)
#DynamicUpdate(value = true)
public class Human {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, name = "name")
private String name;
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
#JoinColumn(name = "parent_id")
private Human parent;
#OneToMany(fetch=FetchType.LAZY, mappedBy ="parent")
private Collection<Human> children;
// getters and setters ...
}
HumanDao.java
#Repository
public interface HumanDao extends CrudRepository<Human, Long>{
#Query(value =
"SELECT * " +
"FROM humans " +
"WHERE parent_id = ?1",
nativeQuery = true)
public Iterable<Human> getChildren(Long parentId);
}
TestClass.java
public class TestClass {
#Autowired
private HumanDao dao;
public void test(Long parentId) {
Iterable<Human> children = dao.getChildren(parentId);
System.out.println(children);
}
}
application.properties:
spring.datasource.url=jdbc:mysql://localhost/db
spring.datasource.username=username
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.max_fetch_depth=1
Database:
ID | Name | Parent
1 | Mike | null
2 | John | 1
3 | Bill | 2
4 | Carl | 3
Problem: When I getChildren for Mike, I expect to see only John there, but I get all the references up to Carl included.
How can I limit the depth of this self referencing entity so it doesn't go deeper than a specified number limit.
I constructed my entity following this example (step 3)
Update: I found someone with a similar problem, but he didn't solve it either.

Composite table with Hibernate Envers

I have an application with a composite table holding one extra column. It all works fine, until we add Hibernate Envers (#Audited).
org.hibernate.MappingException: Unable to read the mapped by attribute for responseDomainCodes in no.pack.response.ResponseDomainCode
I am happy to provide more detailed information if necessary, however, at this time I am not sure what would be relevant.
The tables look like this, and is a pretty standard composite key table, with one extra column.
Database schema
+-----------+---------+
| CODE | TYPE |
+-----------+---------+
| category | VARCHAR |
| code | VARCHAR |
+-----------+---------+
|
|
+----------------------+---------+
| RESPONSE_DOMAIN_CODE | TYPE |
+----------------------+---------+
| response_domain_id | KEY |
| code_id | KEY |
| rank | VARCHAR |
+----------------------+---------+
|
|
+--------------------+------+
| RESPONSE_DOMAIN | TYPE |
+--------------------+------+
| response_domain_id | PK |
| response_kind_id | FK |
+--------------------+------+
ResponseDomain.java
#Entity
#Table(name = "responseDomain")
public class ResponseDomain implements Serializable {
#Id
#Column(name = "responseDomain_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "respons_kind_id")
private ResponseKind responseKind;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "pk.responseDomain", cascade = CascadeType.ALL)
private Set<ResponseDomainCode> responseDomainCodes = new HashSet<>();
//Omitted rest.
}
Code.java
#Entity
#Table(name = "code")
public class Code implements Serializable {
#Id
#Column(name = "code_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String category;
private String code;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "pk.code", cascade = CascadeType.ALL)
private Set<ResponseDomainCode> responseDomainCodes = new HashSet<>();
//Omitted rest
}
ResponseDomainCode.java
#Entity
#Table(name = "responseDomain_code")
#AssociationOverrides(value = {
#AssociationOverride(name = "pk.responseDomain",
joinColumns = #JoinColumn(name = "responseDomain_id")),
#AssociationOverride(name = "pk.code",
joinColumns = #JoinColumn(name = "code_id"))
})
public class ResponseDomainCode implements Serializable {
#EmbeddedId
private ResponseDomainCodeId pk = new ResponseDomainCodeId();
#Column(name = "rank")
private String rank;
public ResponseDomainCodeId getPk() {
return pk;
}
public void setPk(ResponseDomainCodeId pk) {
this.pk = pk;
}
public String getRank() {
return rank;
}
public void setRank(String rank) {
this.rank = rank;
}
#Transient
public ResponseDomain getResponseDomain() {
return getPk().getResponseDomain();
}
public void setResponseDomain(ResponseDomain responseDomain) {
this.getPk().setResponseDomain(responseDomain);
}
#Transient
public Code getCode() {
return getPk().getCode();
}
public void setCode(Code code) {
this.getPk().setCode(code);
}
//Omitted rest
}
ResponseDomainCodeId.java
#Embeddable
public class ResponseDomainCodeId implements Serializable {
#ManyToOne
private ResponseDomain responseDomain;
#ManyToOne
private Code code;
public ResponseDomainCodeId() {
}
public ResponseDomain getResponseDomain() {
return responseDomain;
}
public void setResponseDomain(ResponseDomain responseDomain) {
this.responseDomain = responseDomain;
}
public Code getCode() {
return code;
}
public void setCode(Code code) {
this.code = code;
}
//Omitted rest
}
With the help of #adamw I managed to solve this, by changing my mapping.
Instead of using a composite key, a table with its own unique ID was generated.
+----------------------+------------+
| RESPONSE_DOMAIN_CODE | TYPE |
+----------------------+------------+
| id | PK(BIGINT) |
| response_domain_id | BIGINT |
| code_id | BIGINT |
| rank | VARCHAR |
+----------------------+------------+
Now instead of using #Embeddable and #EmbeddedId I have a #ManyToOne and #OneToMany annotation on either side, and query based on ResponseDomain.
This enable full version audit control using Hibernate Envers also in relations like this.
I hope this will be helpful for someone at some point.

How to map self join with composite key once the foreign key is part of the primary key

I am running into the following problem: I must map my Employee class to the following database schema (where id1 and id2 is a composite primary key):
--------------------------
| id1 | id2 | ManagerId2 |
--------------------------
| 1 | 1 | NULL | <--- Have no manager / Can be manager of many
--------------------------
| 1 | 2 | 1 | <--- Is manager / Has manager 1-1
--------------------------
| 1 | 3 | 1 | <--- Is manager / Has manager 1-1
--------------------------
I am aware that a foreign key must have the same columns as the primary key it references (same number of columns). The point is that once an Employee is inserted with id1 = 1 it must only reference managers with id1 = 1. A way to keep integrity and avoid scenarios like the following:
---------------------------------------
| id1 | id2 | ManagerId1 | ManagerId2 |
---------------------------------------
| 1 | 1 | NULL | NULL | <--- Have no manager / Can be manager of many
---------------------------------------
| 2 | 1 | NULL | NULL | <--- Have no manager / Can be manager of many
---------------------------------------
| 1 | 2 | 2 | 1 | <--- THIS IS NOT ALLOWED
---------------------------------------
| 1 | 3 | 2 | 1 | <--- NOR THIS
---------------------------------------
So far the best that I got is the following mapping (although it creates the table as expected it simply doesn't populates the ManagerId2 field):
#Entity
#Table(name="Employee")
public class Employee {
public Employee(){
}
#EmbeddedId
private EmployeeId id;
public void setId(EmployeeId id) {
this.id = id;
}
public EmployeeId getId() {
return id;
}
#ManyToOne(cascade={CascadeType.ALL})
#JoinColumns({
#JoinColumn(name = "id1", referencedColumnName = "id1", insertable=false, updatable=false), //its fine to have this attribute not insertable nor updatable
#JoinColumn(name = "id2_manager", referencedColumnName = "id2", insertable=false, updatable=false) //but I must be able to update this one!
})
private Employee manager;
public Employee getManager() {
return manager;
}
public void setManager(Employee manager) {
this.manager = manager;
}
}
#Embeddable
public class EmployeeId implements Serializable{
public EmployeeId() {
}
public EmployeeId(int id1, int id2) {
this.id1 = id1;
this.id2 = id2;
}
private int id1;
private int id2;
public int getId1() {
return id;
}
public void setId(int id1) {
this.id1 = id1;
}
public int getId2() {
return id2;
}
public void setId2(int id2) {
this.id2 = id2;
}
//hashCode and equals properly overriden
}
After goggling the entire day it seems that I am not able to find anything!
Can someone kindly show me what I am doing wrong, or point to any good resources?
PS.: I can't change the db schema, it is not an option
Im not sure how to do this with annotations, but ive found a workaround that might help you.
#Entity
#Table(name = "Employee")
public class Employee {
#EmbeddedId
private EmployeeId id;
private Integer id2_manager;
#PrePersist
#PreUpdate
public void prePersistUpdate() {
if (manager != null)
id2_manager = manager.getId().getId2();
}
public Employee() {
}
public void setId(EmployeeId id) {
this.id = id;
}
public EmployeeId getId() {
return id;
}
#ManyToOne(cascade = { CascadeType.ALL })
#JoinColumns({ #JoinColumn(name = "id1", referencedColumnName = "id1", insertable = false, updatable = false),
#JoinColumn(name = "id2_manager", referencedColumnName = "id2", insertable = false, updatable = false, nullable = true) })
private Employee manager;
public Employee getManager() {
return manager;
}
public void setManager(Employee manager) {
this.manager = manager;
}
}
to test
static EntityManagerFactory emf;
static EntityManager em;
public static void main(String[] args) {
emf = Persistence.createEntityManagerFactory("unit");
em = emf.createEntityManager();
Employee manager = new Employee();
manager.setId(new EmployeeId(1, 1));
Employee e1 = new Employee();
e1.setId(new EmployeeId(1,2));
e1.setManager(manager);
em.getTransaction().begin();
em.persist(manager);
em.persist(e1);
em.getTransaction().commit();
Employee e2 = em.find(Employee.class, new EmployeeId(1, 2));
System.out.println(e2.getManager().getId().getId1() + "-" + e2.getManager().getId().getId2());
// prints 1-1
}

Categories

Resources