How i can add based and descendant class in one table with Hibernate?
Based class
public class Id{
protected int id
}
Descendant class
public classs User exetends Id{
public String username;
public String password;
}
In one table USERS with properties: id, username, password.
From what i understood.
You want to have a Base entity. And that Base entity should be extended by other Descendant entities. In other words the Descendant should be having all the properties from the Base entity. One use case for this design is to have common properties(id, createdDate, updatedDate) for all Descendant class in a Base entity class.
One way to go with this is:
BaseEntity(Base Class)
#MappedSuperclass
public class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
//..setter and getters
}
User(Descendant Class)
#Entity
#Table(name = "user")
public class User extends BaseEntity {
#Column(name = "name")
private String name; //User specific properties
//..setters and getters here
}
Firstly you need to mark them with #Entity to show that they are tables, then in the top level set the table name with #Table. To use inheritance, you need to select a strategy: for this instance you want SINGLE_TABLE. Then in order to select the correct type a #DiscriminatorColumn is required, setting the column name and the type of discriminator - in this case I have chosen INTEGER. You'll need to also add to the type annotation updatable = false and insertable = false so they cannot be modified.
#Entity
#Table(name = "TABLE_NAME")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "TYPE",discriminatorType = DiscriminatorType.INTEGER)
public class Id {
#Column(name="TYPE", insertable=false, updatable=false)
private Integer type;
}
On your sub classes you need to mark them with #Entity and a #DiscriminatorValue (in this case 1).
#Entity
#DiscriminatorValue(value = "1")
public classs User exetends Id{
public String username;
public String password;
}
Related
The background of this project is that there are two types of users, suscriber users and amdin users. There are also two types of sucriber users, students and professors. Admin users can register new classrooms, and suscriber users can suscribe to classrooms to see different information such as temperature etc.
The problem is that I have to map a ManyToMany bidirectional relationship between clasrooms and suscriber users and I'm getting the following error in the Classroom class:
'Many To Many' attribute value type should not be 'SuscriberUser'
and this exception:
org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class
This is my code:
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class User implements IUser {
private String name;
// some other fields
// constructors
// getters and setters
}
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class SuscriberUser extends User implements ISuscriberUser {
#ManyToMany(mappedBy = "suscribers")
private ArrayList<Classroom> classroomSubscriptions;
// constructors
// getters and setters
}
For example one concrete class of SuscriberUser:
#Entity
#Table(name = "student")
public class Student extends SuscriberUser {
#Id
private int studentId;
// constructors
// getters and setters
}
#Entity
#Table(name = "classroom")
public class Classroom implements IClassroom {
#Id
private int internalId;
// other fields
#ManyToMany()
#JoinTable(name = "suscribers")
private ArrayList <SuscriberUser> suscribers;
// constructors
// getters and setters
}
I have also tried using #MappedSuperclass in both classes User and SuscriberUser, but it doesn't work. I guess it's because both abstract classes don't have an id.
How can I solve this?
The User class is just a collector of fields, therefore it can become a #MappedSuperClass.
#MappedSuperClass
public abstract class User implements IUser {
private String name;
// some other fields
// constructors
// getters and setters
}
If you use #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS), you have a table per class, so you need to:
remove abstract
add #Entity annotation
add #Table to define a name
add #Id to the id column.
#Entity
#Table(name = "subscriber_user")
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class SuscriberUser extends User implements ISuscriberUser {
#Id
private int id;
#ManyToMany(mappedBy = "suscribers")
private List<Classroom> classroomSubscriptions;
// constructors
// getters and setters
}
More info here.
The student class does not need an id column because is in the parent class.
#Entity
#Table(name = "student")
public class Student extends SuscriberUser {
// constructors
// getters and setters
}
Pay attention that the join table is another one. This table contains the relations between students and classrooms and it can be named subscription.
#Entity
#Table(name = "classroom")
public class Classroom implements IClassroom {
#Id
private int internalId;
// other fields
#ManyToMany
#JoinTable(
name = "subscription",
joinColumns = #JoinColumn(name = "internalId"), // id class room
inverseJoinColumns = #JoinColumn(name = "id")) // id user
private List<SuscriberUser> suscribers;
// constructors
// getters and setters
}
I have a doubt about how the modeling of my entity would be. Come on, I have a table in the database that serves to save documents from my system, this table has the columns id, fk_id (element foreign key), fk_table (entity name) and file_name (stores the name of my file) .
I did a lot of research before posting my question here, but I didn't find anything related to it, what would my entities, user, patient and doctor?
DB:
id
fk_id
fk_table
file_name
1
21
user
test1.jpg
2
32
doctor
test2.pdf
3
61
user
test10.pdf
4
100
patient
test5.jpg
Class:
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String LastName;
// What would a one-to-many relationship look like?
}
public class patient{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
// What would a one-to-many relationship look like?
}
You can use #Where. But be aware that #Where is a Hibernate annotation. It's not in the JPA standard.
For example in the User entity: (I assume that your table is mapped to an entity called Document)
#Where( clause = "fk_table = 'user'")
#JoinColumn(name = "fk_id")
#OneToMany
private List<Document> documents = new ArrayList<>( );
The following is based only on standard JPA annotations. The idea is to create an inheritance hierarchy for the documents table. The base is:
#Entity
#Table(name = "XX_DOCUMENT")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "fk_table")
public abstract class BaseDocument {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#Column(name = "file_name")
private String fileName;
}
Here we define that all entities extending this will go to the same table, with the fk_table column to discriminate. The entities extending it are defined as follows:
#Entity
#DiscriminatorValue("doctor")
public class DoctorDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Doctor doctor;
}
#Entity
#DiscriminatorValue("patient")
public class PatientDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Patient patient;
}
// and so on
The interesting thing is that we are reusing the column fk_id to point to the right table. From a small experiment, Hibernate seems to not have problems with it. I would suggest that you manage the DB creation another way just to be safe.
The Doctor, Patient etc need not have a common base class, e.g.:
#Entity
#Table(name = "XX_DOCTOR")
public class Doctor {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "doctor")
private Collection<DoctorDocument> documents = new ArrayList<>();
// any doctor-specific fields
}
#Entity
#Table(name = "XX_PATIENT")
public class Patient {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "patient")
private Collection<PatientDocument> documents = new ArrayList<>();
// any patient-specific fields
}
// and so on
You can read a (doctor, patient, ...)'s documents from the relevant collection. You can even query BaseDocument instances based on any criteria.
You can even go ahead and do more fabcy stuff with the Java code. E.g. define an interface HasDocuments:
public interface HasDocuments<D extends BaseDocument> {
Collection<D> getDocuments();
}
Doctor, Patient, ..., implements this, so they can all be treated the same way.
I am creating entities that are the same for two different tables. In order do table mappings etc. different for the two entities but only have the rest of the code in one place - an abstract superclass. The best thing would be to be able to annotate generic stuff such as column names (since the will be identical) in the super class but that does not work because JPA annotations are not inherited by child classes. Here is an example:
public abstract class MyAbstractEntity {
#Column(name="PROPERTY") //This will not be inherited and is therefore useless here
protected String property;
public String getProperty() {
return this.property;
}
//setters, hashCode, equals etc. methods
}
Which I would like to inherit and only specify the child-specific stuff, like annotations:
#Entity
#Table(name="MY_ENTITY_TABLE")
public class MyEntity extends MyAbstractEntity {
//This will not work since this field does not override the super class field, thus the setters and getters break.
#Column(name="PROPERTY")
protected String property;
}
Any ideas or will I have to create fields, getters and setters in the child classes?
Thanks,
Kris
You might want to annotate MyAbstractEntity with #MappedSuperclass class so that hibernate will import the configuration of MyAbstractEntity in the child and you won't have to override the field, just use the parent's. That annotation is the signal to hibernate that it has to examine the parent class too. Otherwise it assumes it can ignore it.
Here is an example with some explanations that may help.
#MappedSuperclass:
Is a convenience class
Is used to store shared state & behavior available to child classes
Is not persistable
Only child classes are persistable
#Inheritance specifies one of three mapping strategies:
Single-Table
Joined
Table per Class
#DiscriminatorColumn is used to define which column will be used to distinguish between child objects.
#DiscriminatorValue is used to specify a value that is used to distinguish a child object.
The following code results in the following:
You can see that the id field is in both tables, but is only specified in the AbstractEntityId #MappedSuperclass.
Also, the #DisciminatorColumn is shown as PARTY_TYPE in the Party table.
The #DiscriminatorValue is shown as Person as a record in the PARTY_TYPE column of the Party table.
Very importantly, the AbstractEntityId class does not get persisted at all.
I have not specified #Column annotations and instead are just relying on the default values.
If you added an Organisation entity that extended Party and if that was persisted next, then the Party table would have:
id = 2
PARTY_TYPE = "Organisation"
The Organisation table first entry would have:
id = 2
other attribute value associated specifically with organisations
#MappedSuperclass
#SequenceGenerator(name = "sequenceGenerator",
initialValue = 1, allocationSize = 1)
public class AbstractEntityId implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "sequenceGenerator")
protected Long id;
public AbstractEntityId() {}
public Long getId() {
return id;
}
}
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "PARTY_TYPE",
discriminatorType = DiscriminatorType.STRING)
public class Party extends AbstractEntityId {
public Party() {}
}
#Entity
#DiscriminatorValue("Person")
public class Person extends Party {
private String givenName;
private String familyName;
private String preferredName;
#Temporal(TemporalType.DATE)
private Date dateOfBirth;
private String gender;
public Person() {}
// getter & setters etc.
}
Hope this helps :)
Mark the superclass as
#MappedSuperclass
and remove the property from the child class.
Annotating your base class with #MappedSuperclass should do exactly what you want.
This is old, but I recently dealt with this and would like to share my solution. You can add annotations to an overridden getter.
#MappedSuperclass
public abstract class AbstractEntity<ID extends Serializable> implements Serializable {
#Column(name = "id", nullable = false, updatable = false)
#Id
private ID id;
public ID getId() {
return id;
}
...
}
#Entity
#Table(name = "address")
public final class Address extends AbstractEntity<UUID> implements Serializable {
...
#Override
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
public final UUID getId() {
return super.getId();
}
...
}
I am designing two entities, one called Country and one called CountryDetail. From the perspective of tables, the COUNTRY table will be the parent table, and the COUNTRY_DETAIL table will be the child table. In the COUNTRY table, there will be a unique attribute called COUNTRY_CODE (note this is not a primary key; the primary key will be a numeric sequence based value). This code will be a foreign key to connect to the child table, and in this child table, each COUNTRY_CODE from the parent table will have 3 entries to represent the name of the country in 3 different languages. Following are the entity classes:
Country.java
#Entity
public class Country
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "COUNTRY_ID")
private long id;
#Column(name="COUNTRY_CODE", nullable = false, unique = true)
private String countryCode;
/*public getters*/
}
CountryDetail.java
#Entity
public class CountryDetail
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "COUNTRY_DETAIL_ID")
private long id;
#ManyToOne
#JoinColumn(name="COUNTRY_CODE", referencedColumnName = "COUNTRY_CODE")
private Country country;
#Column(nullable = false)
private String languageCode;
#Column(nullable = false, unique = true)
private String countryNameInLanguage;
/*public getters*/
}
My question is, how can I write a custom "findBy..." interface method inside an extension of JpaRepository that is typed to a Country that would return me a collection of CountryDetail elements that match an input parameter for the languageCode attribute of the CountryDetail class?
public interface CountryRepository extends JpaRepository<Country, Long>
I know how to do it if the repository was typed to CountryDetail instead of Country, but I would like to know how to do it going via the parent entity rather than via the child entity directly, even though the input parameter (languageCode) exists only in the child entity.
Thank you.
It is possible but complicated : Please see an example below :
Parent :
#Entity
#Table(name="PARENT")
public class Parent {
#Id
#Column(name="PARENT_ID")
private int parentId;
#Column(name="PARENT_NAME")
private String parentName;
#OneToOne(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private Child child;
}
Child :
#Entity
#Table(name="CHILD")
public class Child {
#Id
#Column(name="CHILD_ID")
private int childId;
#Column(name="CHILD_NAME")
private String childName;
#OneToOne(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private Parent parent;
}
Define an interface to load child entity from parent repository :
ChildEntity:
public interface ChildEntity {
#Value("#{target.child.childId}")
int getChildId() ;
#Value("#{target.child.childName}")
String getChildName();
}
Parent Repository :
public interface ParentRepository extends JpaRepository<Parent, Integer> {
public ChildEntity findByParentName(String parentName);
}
Test class:
ChildEntity chi=rep.findByParentName("<<NAME>>");
System.out.println(chi.getChildId()+" "+chi.getChildName());
Output:
CHILD ID 1000 CHILD NAME child1
I have an entity, that i'd like to join OneToOne with a table with a composite key (Omitting getters/setters):
#Entity
#Table(name = "parent")
public class Parent {
#Id
private String parentId;
#Column(name = "data")
private String data;
#OneToOne
private Child child;
}
And:
#Entity
#IdClass(ChildKey.class)
#Table(name = "child")
public class Child{
#Id
private String parentId;
#Id
private String username;
#Column(name = "data")
private String childData;
}
public class ChildKey implements Serializable {
private String parentId;
private String username;
}
Parent does not have a notion of the 'username' field in the Child entity. I need to pass this in as criteria. In the DB, the primary key of child is on parentId and username.
If I don't specify a JoinColumn, hibernate attempts to map using fields child_username and child_parentId. If I specify only one Joincolumn, I get a broken mapping. If I specify both JoinColumns, I have no column on parent to specify.
How can I map this class and pass in the username as criteria? (it is coming from authentication data) Or how can I do this in a different way if I'm off track.
You might be able to use a Derived Identity.
The Parent class would remain the same; but you would specify a #OneToOne mapping back to the child's parent and the Child and ChildKey classes would look like this:
#Entity
#IdClass(ChildKey.class)
#Table(name = "child")
public class Child{
#Id
#OneToOne(mappedBy="child")
private Parent parent;
#Id
private String username;
#Column(name = "data")
private String childData;
}
public class ChildKey implements Serializable {
private String parent; // name matches name of the #Id field and type matches type of Parent #Id field
private String username; // name and type match those of the #Id field
}
Derived identity is discussed in JPA 2.1 spec, section 2.4.1.
What I ended up doing was defining a #Filter on the Child class, like so:
#Entity
#IdClass(ChildKey.class)
#Table(name = "child")
#FilterDef(name = "usernameFilter", parameters = {
#ParamDef( name = "username", type="string")
})
public class Child { ... }
On the Parent class, I annotated the collection with a reference to the filter:
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "parentId")
#Filter(name="usernameFilter", condition = "username = :username")
private List<Child> children;
Finally, in my DAO, I parameterized the filter by name like so:
Filter filter = currentSession().enableFilter("usernameFilter");
filter.setParameter("username", user.getUsername());
Doing this resulted in the exact SQL I had in mind, which is an additional clause in the JOIN criteria with a variable:
SELECT
...
FROM
parent this_
LEFT OUTER JOIN
child child_ ON this_.parentId = child_.parentId
AND child_.username = ?
I might not have been clear about what end result I was looking for in my original question. Posting this answer in case it helps someone else.