How to redefine an attribute in inherited classes in JPA - java

I have a generic Database structure which can store several user-defined records. For example, the main table is RECORD and the columns are STRING01, STRING02, [...], NUM01, NUM02 etc.
I know this is a bit weird, but it has advantages as well as disadvantages. However, this structure exists and can't be changed. Now I want to create some JPA classes.
First, I created an abstract class RECORD as follows (the Annotations are placed on the gettersthe example is just simplified):
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="TYPE", discriminatorType=DiscriminatorType.STRING)
public abstract class Record {
#Id
private long id;
#Column(name="STRING01")
private String string01;
#Column(name="STRING02")
private String string02;
#Column(name="NUM01")
private BigDecimal num01;
}
Then, I created specific classes inherited from RECORD:
#Entity
#DiscriminatorValue("Person")
public class Person extends Record {
#Transient
public String getFirstName() {
return getString01();
}
public void setFirstName(String name) {
setString01(name);
}
#Transient
public BigDecimal getWeight() {
return getNum01();
}
public void setWeight(BigDecimal weight) {
setNum01(weight);
}
}
This works fine, as I can query RECORD for a PERSON's primary key (via EntityManager.find()) and get a Result as instance of PERSON. I can query for FirstName and Weight without having to know the generic column names.
However, if I write my own JPA Query like SELECT p FROM Person p WHERE p.firstName = 'Michael', it fails. firstName is transient, and here I have to use the generic name string01.
Is there some way of overriding the base class' attribute name in JPA? Maybe there's a vendor-specific solution (I'm using EclipseLink)?

You can try and map multiple attributes to the same column.
The additional attribute would then be annotated with #Column( name = "column name", insertable = false, updatable = false, nullable = false ).
Alternatively, you might be able to replace/enhance the JPQL resolver in order to internally map p.firstName to p.string01, but that would be EclipseLink specific, and I don't really know if that's even possible. Take this as just a hint what to look for.

Related

Implicit polymorphism vs Explicit polymorphism in Hibernate

I have read the O/R Mapping of Hibernate and I just can't seem to get past the part on polymorphism.
According to https://docs.jboss.org/hibernate/orm/5.0/manual/en-US/html/ch05.html,
Implicit polymorphisms means that instances of the class will be
returned by a query that names any superclass or implemented interface
or class, and that instances of any subclass of the class will be
returned by a query that names the class itself
whereas
Explicit polymorphisms means that class instances will be returned
only by queries that explicitly name that class. Queries that name the
class will return only instances of subclasses mapped
I just want to understand how these 2 work. Can somebody explain these terms using an example(doesn't have to be too complex) with the use of code? I would appreciate your help
First of all the org.hibernate.annotations.Entity annotation is deprecated now. You should use the #Polymorphism annotation instead.
Now, imagine that you have the following schema:
create table TST_STUDENT
(
st_id int not null,
st_name varchar(200),
primary key (st_id)
);
insert into TST_STUDENT values (1, 'Kostya'), (2, 'Yulia'), (3, 'Borya'), (4, 'Misha');
create table TST_TEACHER
(
tcr_id int not null,
tcr_first_name varchar(200),
tcr_last_name varchar(200),
primary key (tcr_id)
);
insert into TST_TEACHER values (1, 'Mikhail', 'Bulgakov'), (2, 'Leo', 'Tolstoy');
and the following mapping:
public interface Person
{
Long getId();
String getName();
}
#Entity
#Table(name = "TST_STUDENT")
public class Student implements Person
{
#Id
#Column(name = "st_id")
private Long id;
#Column(name = "st_name")
private String name;
public Student()
{
}
// getters / setters
}
and Teacher entity:
import org.hibernate.annotations.Polymorphism;
import org.hibernate.annotations.PolymorphismType;
#Entity
#Table(name = "TST_TEACHER")
// #Polymorphism(type = PolymorphismType.EXPLICIT)
public class Teacher implements Person
{
#Id
#Column(name = "tcr_id")
private Long id;
#Column(name = "tcr_first_name")
private String name;
#Column(name = "tcr_last_name")
private String lastName;
public Teacher()
{
}
// getters / setters
}
Now, if you run the following query:
List<Person> persons = em.createQuery("select p from com.your.entities.Person p", Person.class).getResultList();
you will get all rows from the TST_STUDENT table plus all rows from the TST_TEACHER table.
But, if you uncomment this line:
#Entity
#Table(name = "TST_TEACHER")
#Polymorphism(type = PolymorphismType.EXPLICIT) // now we use explicit polymorphism for the Teacher entity
public class Teacher implements Person
The mentioned above query will return only rows from the TST_STUDENT table.
This is what this annotation mean.
By default, when you query a base class entity, the polymorphic query will fetch all subclasses belonging to the base type. You can even query interfaces or base classes that don’t belong to the JPA entity inheritance model.
P.S. See also this part of documentation.

JPA discriminator undesired

I know there are several questions on this argument, but I think mine is a bit different.
I'm trying to use JPA notation instead of XML mapping. In my queries always there's an undesired dtype column, and I don't want to use neither discriminator column and formula.
I have four classes as follow:
The first named ObjectUUID. All classes extend this super class.
This class is used only to define id field, as follow:
#MappedSuperclass
public class ObjectUUID {
#Id
#GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
protected String id;
// getter and setter and other static methods
}
Then I have another class, named TrackSys where I define other fields (as insert date and update date) as follow:
#MappedSuperclass
public class TrackSys extends ObjectUUID{
#Column(name="dt_ins")
private CustomCalendar dtInsert;
#Column(name="dt_upd")
private CustomCalendar dtUpdate;
// getter and setter
}
the third and the forth classes are beans mapped on DB, as follow:
#Entity
#Table(name="patient")
public class PatientUUID extends TrackSys {
}
#Entity
#Table(name="patient")
public class Patient extends PatientUUID {
#Column(name="surname")
private String surname;
#Column(name="name")
private String name;
#Column(name="cf")
private String cf;
// getter and setter
}
I define a repository to query my patient table, as follow:
public interface PatientRepository extends JpaRepository<Patient, Long> {
List<Patient> findBySurname(String surname);
}
When my Patient query runs, the generated SQL is the follow:
select patient0_.id as id2_2_, patient0_.dt_ins as dt_ins3_2_,
patient0_.dt_upd as dt_upd4_2_, patient0_.cf as cf7_2_,
patient0_.surname as surname8_2_, patient0_.name as name11_2_,
from patient patient0_ where patient0_.dtype='Patient'
and patient0_.surname=?
Now...
I don't want dtype column and I don't want to use discriminator formula.
With the XML mapping this is possible without particular properties to specify.
With JPA annotation I try in order:
Use #Inheritance(strategy = InheritanceType.SINGLE_TABLE) but dtype is always present
Use #Inheritance(strategy = InheritanceType.JOINED) but dtype is always present
Now, my feeling versus dtype is only hate, how can I get this simple query, as follow:
select patient0_.id as id2_2_, patient0_.dt_ins as dt_ins3_2_,
patient0_.dt_upd as dt_upd4_2_, patient0_.cf as cf7_2_,
patient0_.surname as surname8_2_, patient0_.name as name11_2_,
from patient patient0_ where patient0_.surname=?

Dynamic JPA #JoinColumn Name

I am not very experienced with JPA and was curious if the following is possible.
Say I have a class Project as follows:
#Entity
public class Project {
#Id
private String projectCode;
private String departmentId;
/*
* Is something like this possible with JPA?
*/
if (departmentId == null) {
#JoinColumn(name = "projectCode", referencedColumnName = "assignedProject")
} else {
#JoinColumn(name = "departmentId", referencedColumnName = "id")
}
#OneToMany(targetEntity = Employee.class)
private List<Employee> contributors;
// getters/setters
}
So I would like to populate the contributors list based on the presence of departmentId.
Is this possible with JPA? Or will I have to specify two List<Employee> fields, mapped by both variables, and preform proper checks within my application logic?
Thanks for your help.
/*
* Is something like this possible with JPA?
*/
if (departmentId == null) {
#JoinColumn(name = "projectCode", referencedColumnName = "assignedProject")
} else {
#JoinColumn(name = "departmentId", referencedColumnName = "id")
}
No, this isn't possible with JPA and you'll be glad that it isn't.
You can achieve what you want by using inheritance in Java. Begin by creating an abstract entity that contains all the common fields of your table. Then you can create an entity subclass with a projectCode attribute and another entity subclass with a departmentId attribute.
At the RDBMS level, for a simple object model like the one we just built, a single table can be mapped. In the abstract entity, you would annotate as follows to achieve this:
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "DTYPE", discriminatorType = STRING, length = 1)
#DiscriminatorValue("?")
#Entity
public abstract class Project {
:
:
}
#DiscriminatorValue("P")
public class ProjectCodeProject extends Project {
:
:
}
Remember that RDBMS has no knowledge or notion of inheritance. Inheritance exists only on the Java side. In the database, inheritance is represented by metadata. The "Discriminator" is a special column (here named "DTYPE") that appears in your database table that informs JPA which subclass a particular column represents. In the above, the code "P" was chosen to represent database records that have a PROJECTCODE attribute rather than a DEPARTMENTID attribute.
Using a class hierarchy like this would enable you to have a table whose rows can have either a departmentId or a projectCode as an attribute (not both). Because rows of the table are all Projects, developing common logic in Java to work with the subtypes ought to be relatively straightforward.

JPA/Hibernate - InheritanceType.JOINED behaves like InheritanceType.TABLE_PER_CLASS

I'm trying to implement a very simple inheritance model in Hibernate. Basically I have a single superclass which can be called A, and several subclasses all of which inherit from A. Since the behavior I'm seeing is the same for all of them, they can just be referred to as B.
What I'm trying to arrive at is what's described here in section 6.2. Basically, there should be a table for A that contains its fields, and a table for B that contains only the fields that are distinct to the subclass, plus a join column back to the table for A. I am using Hibernate's automatic schema generation (enabled for the development persistence-unit only).
What I see when I look at the schema, however, is a table for A the contains its fields (correct), and a table for B which contains all the fields in A (incorrect), plus the fields added in B. My classes are annotated as follows:
#Entity
#Table(name="A")
#Inheritance(strategy = InheritanceType.JOINED)
public class A implements Serializable {
protected long id;
protected Date createDate;
protected String title;
protected boolean hidden;
public A() {
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public long getId() {
return id;
}
#Column(nullable = false)
#Temporal(TemporalType.TIMESTAMP)
public Date getCreateDate() {
return createDate;
}
#Column(nullable = false)
public boolean isHidden() {
return hidden;
}
#Column(nullable = false)
public String getTitle() {
return title;
}
//also setters...
}
#Entity
#Table(name="B")
#PrimaryKeyJoinColumn(name="aId", referencedColumnName="id")
public class B extends A {
private String extraField;
public B() {
super();
}
#Column
public String getExtraField() {
return extraField;
}
//also setter...
}
Any ideas what I've done wrong? Specifically, what I want to see when I look at the generated DB schema is something like:
Table A: {id, createDate, title, hidden}
Table B: {aId, extraField}
...and instead what I get is:
Table A: {id, createDate, title, hidden}
Table B: {id, createDate, title, hidden, extraField}
Is this just not possible using Hibernate's automatic schema generation, or have I screwed up the annotations somewhere?
Your annotation is correct , it should produce the table schema that you want .
But now you get an undesired schema , which is exactly the schema produced using the Table per concrete class strategy (i.e #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)). So , I think one of the possible reason is that the hibernate.hbm2ddl.auto property in your configuration uses the default value , which is update .
The behavior of the update value is :
Hibernate will try to create an
update script to update the database
schema to the current mapping when
the SessionFactory is created.
If an update statement cannot be
performed , it will be skipped (For
example adding a not null column to a
table with existing data)
Hibernate will not delete any data
during the update .(For example , if
a column 's name is changed , it just
add an new column with the new name ,
but still keep the column with the
original name)
So , I think you must use #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) to generate the schema before , which produced the following schema . Table A and Table B do not have any foreign key associations to each other.
Table A: {id, createDate, title, hidden}
Table B: {id, createDate, title, hidden, extraField}
After that , you changed to use #Inheritance(strategy = InheritanceType.JOINED) . During the update schema process , hibernate just updated your scheme by adding a foreign key assocation between the TableA.id and TableB.id . It kept all other columns in Table B . That 's why you get the current schema even though your annotation is correct .
The desired table schema should be generated after you drop Table A and Table B from the DB before starting the hibernate programe . Alternatively , you can set the hibernate.hbm2ddl.auto to create , then hibernate will delete all tables before generating the table schema .

Multiple unique constraints in JPA

Is there a way to specify using JPA that there should be multiple unique constraints on different sets of columns?
#Entity
#Table(name="person",
uniqueConstraints=#UniqueConstraint(columnNames={"code", "uid"}))
public class Person {
// Unique on code and uid
public String code;
public String uid;
// Unique on username
public String username;
public String name;
public String email;
}
I have seen a hibernate specific annotation but I am trying to avoid vendor specific solutions as we are still deciding between hibernate and datanucleus.
The #Table's attribute uniqueConstraints actually accepts an array of these. Your example is just a shorthand for an array with a single element. Otherewise it would look like:
#Table(name="person", uniqueConstraints={
#UniqueConstraint(columnNames={"code", "uid"}),
#UniqueConstraint(columnNames={"anotherField", "uid"})
})
Whenever the unique constraint is based only on one field, you can use #Column(unique=true) on that column.

Categories

Resources