Joining to a certain property of a composite primary key in hibernate - java

I'm using hibernate, and i have two tables (legacy, they cant be restructured) like the following
Table A
#Entity
#Table(name = "tableA")
public Class TableA implements Serializable {
#EmbeddedId private TableAId tableAId;
#OneToOne(mappedBy = "tableA")
private TableB tableB;
// getters, setters, hashCode and equals ommited
}
Composite Id of Table A
#Embeddable
public class TableAId implements Serializable {
protected int id1;
protected String id2;
protected int id3;
// getters, setters, hashCode and equals ommited
}
Table B
#Entity
public class TableB implements Serializable {
#OneToOne
#JoinColumn(name = "tableB", referencedColumnName = "id3")
protected TableA tableA;
// getters, setters, hashCode and equals ommited
}
As can be seen on above code, i need that table B to join table A using one or more (not all) properties of the composite key of the table. I already 've tried with no success, with these three approaches:
Exactly like code above.
Changing referencedColumnName value like this
referencedColumName = "tableAId.id3"
Adding properties that compound the composite key also in the pojo entity of TableA like this:
// TableA
#Column(name = "id1", insertable = false, updatable = false)
private Integer id1;
I if this is even posible??, if it is any help will be appreciated!!

I think the JPA annotation #MapsId might work for you here:
http://docs.oracle.com/javaee/6/api/javax/persistence/MapsId.html
In TableA class, try this for the tableB property:
#MapsId("id3")
#OneToOne(mappedBy = "tableA")
private TableB tableB;
Remove the #JoinColumn annotation from the tableA property in TableB class as well.

Related

How to map 2 tables through non-PK columns with different names in Hibernate

I have 2 tables that may be related to each other through non-PK secondary columns. Moreover, the column names for this match are different in each table. That is,
#Entity
#Table(name = "PLANS_T")
public class Plans {
private Integer id; // PK
//...
private String secondaryIdentifier; // Should be matched with TRAINEES.auxiliaryIdentifier
//...
}
#Entity
#Table(name = "TRAINEES_T")
public class Trainee {
private Integer id; // PK
//...
private String auxiliaryIdentifier; // Should be matched with PLANS.secondaryIdentifier
}
The relationship between PLANS and TRAINEE is Many-to-One: You can have many Plans for a Trainee.
I need to annotate these properly to indicate that PLANS_T.secondaryIdentifier should be used with TRAINEES_T.auxiliaryIdentifier for JOINs (such as in the Criteria API, which needs a Join Path from one table to the other).
But I can't use the typical examples e.g.
#Entity
class Trainee {
#OneToMany(mappedBy = "plan")
private Collection<Plans> plans = new ArrayList<Plans>();
}
#Entity
class Plans {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="auxiliary_identifier") // Where do I specify "secondaryIdentifier", a non-PK column?
private Trainee trainee;
}
I need a way to specify both of the non-PK columns in the annotations. When using Criteria API, these annotations provide the path to create Join paths.
You should correct your mapping in the following way:
#Entity
class Trainee {
#OneToMany(mappedBy = "trainee")
private List<Plans> plans = new ArrayList<Plans>();
}
#Entity
class Plans {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="secondary_identifier", referencedColumnName = "auxiliary_identifier")
private Trainee trainee;
}
The mappedBy of the #OneToMany is the name of the field that owns the relationship. This is trainee field of the Plans entity.
The referencedColumnName of the #JoinColumn is the name of the column referenced by this foreign key column.

Can I define different type for foreign key column from the parent table?

I need to connect to a postgreSQL database in a relation like in the code.
The problem is in the database "id" in table "b" is "bigint" and "b_id" in table "a" is "integer", prod db is not in my control so i can't change it.
"b_id" in "a" is not defined as foreign key, thats probably why postgre did allow that.
As expected, hibernate throws "found [int4 (Types#INTEGER)], but expecting [integer (Types#BIGINT)]"
If i create tables via hibernate it defines "b_id" in table "a" as "bigint" as expected
What would be the best way of validating the schema without changing types in the database?
#Entity
#Table(name = "a")
public class A {
#ManyToOne
#JoinColumn(name = "b_id")
#NotNull
private B bId;
...
}
#Entity
#Table(name = "b")
public class B {
#Id
private Long id;
...
}
You can use columnDefinition element in your A class.
#Entity
#Table(name = "a")
public class A {
#ManyToOne
#JoinColumn(name = "b_id", columnDefinition = "int")
#NotNull
private B bId;
...
}

Hibernate Referencing Embedded ID columns in FK

I'm having a problem mapping multiple #EmbeddedId columns of different entity classes. I have an entity structure like the following (have simplified the entities for readability).
Entity: Action
#Entity
#AttributeOverrides({ #AttributeOverride(name="pk.method", column=#Column(name = "http_method", nullable = false)) })
#AssociationOverrides({ #AssociationOverride(name = "pk.operation", joinColumns = #JoinColumn(name = "id_operation", nullable = false)) })
public class Action {
#EmbeddedId
private ActionId pk = new ActionId();
}
This entity has an #EmbeddedId defined as the following:
#Embeddable
private class ActionId implements Serializable {
#ManyToOne private Operation operation;
private HttpMethod method;
}
The mappings here between the pk columns work fine. The problems come when I attempt to add another entity which is a link table to the Action entity. This link table also has an #EmbeddedId.
Entity: UserAction
#Entity
#AssociationOverrides .. ?
public class UserAction {
#EmbeddedId private UserActionId pk = new UserActionId();
}
The #EmbeddedId class looks like:
#Embeddable
private class UserActionId implements Serializable {
#ManyToOne private Action action;
#ManyToOne private User user;
}
How to I reference the #EmbeddedId fields of the Action class, but only one of the columns is a join column. The other column in the ActionId class is a regular #Column.
I have tried this:
#AssociationOverrides({
#AssociationOverride(name = "pk.action.pk.operation", joinColumns = #JoinColumn(name = "id_operation", nullable = false)) })
But this is giving me an error:
Caused by: org.hibernate.MappingException: Foreign key (FK_gwvslpytxm695kdw5lxqneyss:user_action [useraction])) must have same number of columns as the referenced primary key (useraction[id_operation,http_method])
at org.hibernate.mapping.ForeignKey.alignColumns(ForeignKey.java:110)
at org.hibernate.mapping.ForeignKey.alignColumns(ForeignKey.java:93)
at org.hibernate.cfg.Configuration.secondPassCompileForeignKeys(Configuration.java:1816)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1739)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1424)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1844)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:850)
How do i override the other non-join column method association of the ActionId class in the UserAction entity?

JPA entities: inheritance and one-to-one relationship not working simultaneously

I have the following scheme: TableA1 and TableA2 exist in the database and each is represented by an entity bean. Since they are related, I created an abstract class (TableA, it's an entity but does not exist in the database) where both entities inherit from this class. In addition, TableA has a one-to-one relationship with TableB.
My objective is to query TableB and from there get information of TableA1 or TableA2 depending on the type.
TableA1 and TableA2 each has an id (each table generates automatically a sequential number, so you may have repetition).
In TableB I have two columns that combined represent the foreign key: type and id. Type = 1 means that id is in TableA1. Similarly with TableA2.
My problem is that I don't know how to define these two columns as an external foreign key.
This is what I've got:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#DiscriminatorColumn(name="type")
public abstract class TableA {
#Id
#Column(name = "type")
protected int type;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
protected int id;
#Column(name = "name")
private String name;
// Getters and setters
}
#Entity
#DiscriminatorValue("1")
#Table (name="tableA1")
public class TableA1 extends TableA {
#Column(name="col1")
private String col1;
// Getters and setters
}
#Entity
#DiscriminatorValue("2")
#Table (name="tableA2")
public class TableA2 extends TableA {
#Column(name="col2")
private String col2;
// Getters and setters
}
#Entity
#Table (name="tableB")
public class TableB {
#Id
#Column(name="someId")
private Integer someId;
#Column(name="type")
private int type;
#Column(name="id")
private Integer id;
#OneToOne(optional = false, cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "type"),
#JoinColumn(name = "id" )
})
private TableA tableA;
// Getters and setters
}
Update
Am I looking for the impossible? This is what I found:
Polymorphic relations to non-leaf classes in a table-per-class hierarchy have many limitations. When the concrete subclass is not known, the related object could be in any of the subclass tables, making joins through the relation impossible. This ambiguity also affects identity lookups and queries; these operations require multiple SQL SELECTs (one for each possible subclass), or a complex UNION.
Update 2
TableA1, TableA2 and TableB already exist in the database and have the following structure:
CREATE TABLE TableA1 (
surrogate_key int AUTO_INCREMENT,
some_char char(30),
PRIMARY KEY (surrogate_key)
);
CREATE TABLE TableA2 (
surrogate_key int AUTO_INCREMENT,
some_int int,
PRIMARY KEY (surrogate_key)
);
CREATE TABLE TableB (
surrogate_key int AUTO_INCREMENT,
type int, // if type=1, sk represents the surrogate_key of tableA1
// if type=2, sk represents the surrogate_key of tableA2
sk int,
description varchar(200),
PRIMARY KEY (surrogate_key)
);
Update answer:
Updated to match database
You can use getDiscriminatorValue() to access the DiscriminatorValue.
Define mappings like this:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.INTEGER)
public abstract class TableA implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
#Column(name = "surrogate_key")
protected int id;
#Id
#Column(name = "type")
protected int type;
// Constructors & getters/setters
#Transient
public String getDiscriminatorValue() {
DiscriminatorValue val = this.getClass().getAnnotation(DiscriminatorValue.class);
return val == null ? null : val.value();
}
}
#Entity
#DiscriminatorValue("1")
public class TableA1 extends TableA {
#Column(name = "some_char", length = 1)
private char someChar;
// Constructors & getters/setters & toString/equals
}
#Entity
#DiscriminatorValue("2")
public class TableA2 extends TableA {
#Column(name = "some_int")
private int someInt;
// Constructors & getters/setters & toString/equals
}
#Entity
public class TableB implements Serializable {
#Id
#GeneratedValue
#Column(name = "surrogate_key")
private int id;
#OneToOne
#Cascade(value = CascadeType.SAVE_UPDATE)
#JoinColumns({#JoinColumn(name = "sk", referencedColumnName = "surrogate_key"),
#JoinColumn(name = "type", referencedColumnName = "type")})
private TableA tableA;
#Column(name = "description", length = 200)
private String description;
// Constructors & getters/setters & toString/equals
}
and query like this:
newSession.createQuery("from TableB tb where tb.tableA.type=:type order by tb.id asc").setParameter("type", 1));

can someone please explain me #MapsId in hibernate?

Can someone please explain to me #MapsId in hibernate? I'm having a hard time understanding it.
It would be great if one could explain it with an example and in what kind of use cases is it most applicable?
Here is a nice explanation from Object DB.
Designates a ManyToOne or OneToOne relationship attribute that provides the mapping for an EmbeddedId primary key, an attribute within an EmbeddedId primary key, or a simple primary key of the parent entity. The value element specifies the attribute within a composite key to which the relationship attribute corresponds. If the entity's primary key is of the same Java type as the primary key of the entity referenced by the relationship, the value attribute is not specified.
// parent entity has simple primary key
#Entity
public class Employee {
#Id long empId;
String name;
...
}
// dependent entity uses EmbeddedId for composite key
#Embeddable
public class DependentId {
String name;
long empid; // corresponds to primary key type of Employee
}
#Entity
public class Dependent {
#EmbeddedId DependentId id;
...
#MapsId("empid") // maps the empid attribute of embedded id
#ManyToOne Employee emp;
}
Read the API Docs here.
I found this note also useful: #MapsId in hibernate annotation maps a column with another table's column.
It can be used also to share the same primary key between 2 tables.
Example:
#Entity
#Table(name = "TRANSACTION_CANCEL")
public class CancelledTransaction {
#Id
private Long id; // the value in this pk will be the same as the
// transaction line from transaction table to which
// this cancelled transaction is related
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID_TRANSACTION", nullable = false)
#MapsId
private Transaction transaction;
....
}
#Entity
#Table(name = "TRANSACTION")
#SequenceGenerator(name = "SQ_TRAN_ID", sequenceName = "SQ_TRAN_ID")
public class Transaction {
#Id
#GeneratedValue(generator = "SQ_TRAN_ID", strategy = GenerationType.SEQUENCE)
#Column(name = "ID_TRANSACTION", nullable = false)
private Long id;
...
}
IMHO, the best way to think about #MapsId is when you need to map a composite key in a n:m entity.
For instance, a customer can have one or more consultant and a consultant can have one or more customer:
And your entites would be something like this (pseudo Java code):
#Entity
public class Customer {
#Id
private Integer id;
private String name;
}
#Entity
public class Consultant {
#Id
private Integer id;
private String name;
#OneToMany
private List<CustomerByConsultant> customerByConsultants = new ArrayList<>();
public void add(CustomerByConsultant cbc) {
cbc.setConsultant(this);
this.customerByConsultant.add(cbc);
}
}
#Embeddable
public class CustomerByConsultantPk implements Serializable {
private Integer customerId;
private Integer consultantId;
}
#Entity
public class CustomerByConsultant{
#EmbeddedId
private CustomerByConsultantPk id = new CustomerByConsultantPk();
#MapsId("customerId")
#JoinColumn(insertable = false, updatable = false)
private Customer customer;
#MapsId("consultantId")
#JoinColumn(insertable = false, updatable = false)
private Consultant consultant;
}
Mapping this way, JPA automagically inserts Customer and Consultant ids in the EmbeddableId whenever you save a consultant. So you don't need to manually create the CustomerByConsultantPk.
As he explained Vladimir in his tutorial, The best way to map a #OneToOne relationship is to use #MapsId. This way, you don’t even need a bidirectional association since you can always fetch the Child entity by using the Parent entity identifier.
MapsId lets you use the same primary key between two different entities/tables. Note: when you use MapsId, the CASCADE.ALL flag becomes useless, and you will need to make sure that your entities are saved manually.

Categories