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

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)

Related

Hibernate composite key and overlapping field - how to avoid column duplication

I am facing a problem about how to manage mapping for a specific model.
This is a multitenant application, and we have made the choice of including the "tenant_id" in every entity, so we don't have to make a joint everytime we need to get an entity (in fact, this is the root of my problem...).
The model is as follow :
+--------------------+ +---------------+
| Book | | Author |
+--------------------+ +---------------+
| id (pk) | | id (pk) |
| tenant_id (pk)(fk) | | tenant_id (pk |
| author_id (fk) | | name |
| title | +---------------+
+--------------------+
As you can see, the tenant-id is in each entity, and part of the primary key. We use #IdClass to manage the composite key. Here is the code :
#Data
public class TenantAwareKey implements Serializable {
private UUID id;
private Integer tenantId;
}
#IdClass(TenantAwareKey.class)
#Entity
#Table(name = "BOOK")
#Data
public class Book {
#Id
#GeneratedValue
#Column(name = "ID")
private UUID id;
#Id
#Column(name = "TENANT_ID")
private Integer tenantId;
private String title;
#ManyToOne
#JoinColumns(
value = {
#JoinColumn(referencedColumnName = "id", name = "author_id"),
#JoinColumn(referencedColumnName = "tenant_id", name = "tenant_id", insertable = false, updatable = false)
})
private Author author;
}
#IdClass(TenantAwareKey.class)
#Entity
#Data
public class Author {
#Id
#GeneratedValue
#Column(name = TenantAwareConstant.ENTITY_ID_COLUMN_NAME)
private UUID id;
#Id
#Column(name = TenantAwareConstant.TENANT_ID_COLUMN_NAME)
private Integer tenantId;
private String name;
}
And then, when running my application I ended up with :
Caused by: org.hibernate.AnnotationException: Mixing insertable and non insertable columns in a property is not allowed:
com.pharmagest.durnal.tenant.entity.BookNoDuplicateColumn.author
at org.hibernate.cfg.Ejb3Column.checkPropertyConsistency(Ejb3Column.java:725)
at org.hibernate.cfg.AnnotationBinder.bindManyToOne(AnnotationBinder.java:3084)
(...)
I manage to make it work if I don't try to "mutualize" the tenant_id column, it can be acceptable when I have only one foreign key with this tenant_id, but less and less as the number of foreign key increase, resulting in adding a tenant_id column each time, duplicating information et spoiling memory...
After digging a bit, I found an open issue in Hibernate : https://hibernate.atlassian.net/browse/HHH-6221
It has not been fixed for years... So, my question is : Have you faced a mapping like this, and is there a solution to avoid duplicated columen when I have a foreign-key that share a field with the primary key?
As described here, you can bypass the validation by using #JoinColumnOrFormula for the column id_tenant.
You should map the author's association like this:
#JoinColumnsOrFormulas(
value = {
#JoinColumnOrFormula(column = #JoinColumn(referencedColumnName = "id", name = "author_id")),
#JoinColumnOrFormula(formula = #JoinFormula(referencedColumnName = "tenant_id", value = "tenant_id"))
})

ManyToManyToMany - Joining three tables with Hibernate annotations

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));
}
}

Bug in Hibernate or my misunderstanding of composite Id?

I'm not big guru of JPA, but this behaviour looks strange to me, please comment!
I'm trying to persist nested Objects:
each Employee contains Set of Departments
if Department #Id is declared as #Generated int everything works fine. But it is inconvenient and I try to declare as #Id two fields (employee id + department id). But I get
EJBTransactionRolledbackException: A different object with the same identifier value was already associated with the session
on both merge and persist. Is it OK?
Some details:
Departments are declared this way
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, cascade = CascadeType.ALL)
#JoinColumn(name = "snils")
private Set<DepartmentRecord> departments = new HashSet<>();
the relation is unidirectional and Persons are not declared in Departments
I populate departments this way:
record.getDepartments().add(new DepartmentRecord(snils, department));
Person is created this way:
Person record = em.find(Person.class, snils);
if (record == null)
record = new Person();
record.setSnils(snils);
record.setFullname(fullname);
record.resetLastActive();
on em.persist(record) or em.merge(record) I get an Exception.
Wrapping it to try catch block doesn't help
Transaction cannot proceed: STATUS_MARKED_ROLLBACK
What I need - is just update Person and Departments. Is it possible without boilerplate code (removing cascade, doing separate departments search et c )???
My environment: CentOS7, java8, Wildfly 10.2
Persons table is :
snils | character varying(255) | not null
blocked | integer | not null
fullname | character varying(255) |
lastactive | date |
Indexes:
"hr_workers_pkey" PRIMARY KEY, btree (snils)
Referenced by:
TABLE "hr_departments" CONSTRAINT "fk49rcpg7j54eq6fpf9x2nphnpu" FOREIGN KEY (snils) REFERENCES hr_workers(snils)
Department table is :
snils | character varying(255) | not null
department | character varying(255) | not null
lastactive | date |
Indexes:
"hr_departments_pkey" PRIMARY KEY, btree (snils, department)
Foreign-key constraints:
"fk49rcpg7j54eq6fpf9x2nphnpu" FOREIGN KEY (snils) REFERENCES hr_workers(snils)
Entity Person (actually SNILSRecord):
#Entity
#Table(name = "hr_workers")
public class SNILSRecord implements Serializable {
#Id
private String snils;
private String fullname;
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, cascade = CascadeType.ALL)
#JoinColumn(name = "snils")
private Set<DepartmentRecord> departments = new HashSet<>();
#Temporal(TemporalType.DATE)
private Date lastActive;
private int blocked;
//getters and setters
}
Entity Department:
#Entity
#Table(name = "hr_departments")
public class DepartmentRecord implements Serializable {
#Id
private String snils;
#Id
private String department;
#Temporal(TemporalType.DATE)
private Date lastActive;

JPA OneToMany on composited column

First of all, this is a legacy DB and the schema cannot be changed. Now imagine I've the following tables:
----------- -----------
| table A | | table B |
----------- -----------
| id_A |----->| id_B |
| col_A | | col_B |
----------- -----------
Table A is the master and Table B is the detail.
Both id_A and id_B are strings BUT id_B = id_A + 4 characters.
For instance, if id_A = "0000000123" then there are multiple id_B like the following ones "00000001230001","00000001230002", "00000001230003", ... yes, I know, that should have been another column. As I said this is a legacy DB and I found it that way.
I'm using Spring Data JPA, JPA2 and Hibernate. And what I need is to define the entities:
#Entity
#Table(name="A")
public class A {
#Column(name = "id_A", length = 10, unique = true, nullable = false)
private String idA;
#Column(name = "col_A")
private String colA;
#OneToMany <-- WHAT MORE GOES HERE TO REFERENCE JUST THE SUBSTRING OF THE DETAIL id_B?
private List<B> detail;
}
#Entity
#Table(name="B")
public class B {
#Column(name = "id_B", length = 14, unique = true, nullable = false)
private String idB;
#Column(name = "col_B")
private String colB;
}
I don't know how to reference that what I need is that substr(id_b, 1, 10) = id_A
How can I do that?
The only solution I can see for this is to create a view at the database level. MySQL supports updateable views if certain criteria are met and we should be okay with both reading and writing.
http://dev.mysql.com/doc/refman/5.7/en/view-updatability.html
(Recent versions of MySQL also support Generated columns which may be an alternative to a view (http://mysqlserverteam.com/generated-columns-in-mysql-5-7-5/)
Create a view:
create view VW_TABLE_B
as
select *,
substring(..) as a_id
from B
Entity B is mapped to the view:
#Entity
#Table(name="VW_TABLE_B")
public class B {
#Id
#Column(name = "id_B", length = 14)
private String id;
#Column(name = "col_B")
private String colB;
protected void setId(String id){
this.id = id;
}
}
From a JPA perspective it doesn't matter if we are using a view, concrete table or whatever. The relationship from Entity A to Entity B can then become a standard mapping with join column pointing to the derived value in the view.
#Entity
#Table(name="A")
public class A {
#Id
#Column(name = "id_A", length = 10)
private String id;
#Column(name = "col_A")
private String colA;
#OneToMany
#JoinColumn(name = "a_id")
private List<B> detail;
public void addB(B b){
b.setId(this.id + new DecimalFormmat("0000").format(detail.size() + 1);
detail.add(b);
)
}

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.

Categories

Resources