Springboot JPA/hibernate: how to map one table multiple entities - java

I'm creating a system where I have some entities that have some properties that are common, like address (with street, number, zip etc.) and phone (number, type, etc.) and I don't want to repeat these columns on each entity.
Here' a example:
Student has address and phone
Teacher has multiple addresses (home
and office) and multiple phones (home, mobile, office)
StaffMember
has address and multiple phones (home, mobile and office)
I've used something like that while developing Ruby On Rails using polymorphic associations. I've searched for something like on Java/JPA/Hibernate and couldn't find something much like it. I've found many things about JPA inheritance but I don't quite understand it.
Can you give me a example on how to model it and how to use it?
EDIT
After reading my question I think it's not clear enough, so let me add here the database schema I have:
Student
-------
id bigint
name varchar
birth_date date
...
Teacher
-------
id bigint
name varchar
birth_date date
department varchar
...
StaffMember
-------
id bigint
name varchar
birth_date date
department varchar
function varchar
...
Address
-------
id bigint
street varchar
number int
...
entity_id bigint
entity_type varchar
Phone
-----
id bigint
type varchar
number varchar
...
entity_id bigint
entity_type varchar
And for both Address and Phone the columns entity_id and entity_type are references to Student, Teacher and StaffMember.
But how to map it using Hibernate/JPA?

If you don't mind using Hibernate-specific annotations, there are the #Any and #AnyDef annotations that do what you want. You will need to specify a #MetaValue entry for each entity type related to Address so Hibernate knows how to store the proper value for item_type
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import org.hibernate.annotations.Any;
import org.hibernate.annotations.AnyMetaDef;
import org.hibernate.annotations.MetaValue;
#Entity
public class Address {
#Any(metaColumn = #Column(name = "ITEM_TYPE"))
#AnyMetaDef(idType = "long", metaType = "string",
metaValues = {
#MetaValue(targetEntity = Student.class, value = "STUDENT"),
#MetaValue(targetEntity = Teacher.class, value = "TEACHER")
})
#JoinColumn(name="ITEM_ID")
private Object item;
#Id
#GeneratedValue
private Long id;
#Column
private String type;
#Column
private String street;
#Column
private int number;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Object getItem() {
return item;
}
public void setItem(Object item) {
this.item = item;
}
#Override
public String toString() {
return "Address{" +
"person=" + item +
", id=" + id +
", type='" + type + '\'' +
", street='" + street + '\'' +
", number=" + number +
'}';
}
}
Now, you can just use Address in any bean that has the proper #MetaValue entry, both as #ManyToOne association:
#ManyToOne
protected Address address;
Or a #OneToMany association:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
protected Collection<Address> addresses;
I created a simple test project using Spring Boot and it seemed to work nicely!

I think what you need is to use a generic repository class.
This post http://blog.netgloo.com/2014/12/18/handling-entities-inheritance-with-spring-data-jpa explains pretty much it. Let me know if works for you.

Something along these lines:
#Embeddable
public class Address {
// Fields & accessors
// Do the Phone class in similar fashion
}
#Entity
#DiscriminatorColumn(name = "entity_type")
public abstract class Person {
#Id
private Integer id;
private String name;
#Embedded
private Address homeAddress;
#Embedded
private Phone homePhone;
// Other shared fields & accessors
}
#Entity
public abstract class Employee extends Person {
#Embedded
private Phone mobilePhone;
#Embedded
private Phone officePhone;
//...
}
#Entity
public class Students extends Person {
}
#Entity
public class StaffMember extends Employeee {
}
#Entity
public class Teacher extends Employeee {
#Embedded
private Address officeAddress;
// Accessors & other ...
}

Related

Persist base and subclass with hibernate

I' currently trying to build a complete Spring Boot Rest service with jdbc connection by myself.
At the moment I'm struggling with a minor problem of comprehension regarding hibernate and storing entities.
I have one base class:
#Entity
#Table
public abstract class Person {
private int id;
private String firstName;
private String middleName;
private String lastName;
#Id
#GeneratedValue(generator = "increment")
#GenericGenerator(name = "increment", strategy = "increment")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Column
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
#Column
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
#Column
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
And 2 sub classes:
#Entity
#Table
public class Member extends Person{
private String memberNumber;
#Column
public String getMemberNumber() {
return memberNumber;
}
public void setMemberNumber(String memberNumber) {
this.memberNumber = memberNumber;
}
}
and
#Entity
#Table
public class Supporter extends Person {
private String supporterNumber;
#Column
public String getSupporterNumber() {
return supporterNumber;
}
public void setSupporterNumber(String supporterNumber) {
this.supporterNumber = supporterNumber;
}
}
The base class is abstract because I want to prevent to create a instance of this without specify a person either a member or supporter. But in the database scheme I still want to have 3 tables because of normalization.
Which annotations should I use now the reach this target? How can I link a row of member or supporter to the member now? I'm really confused.
Thanks!
The mapping from class to table is done by hibernate. Since a relational database table and a Java object are kinda different ORM mappers have different strategies how to map between them.
Hibernate can use the following strategies:
MappedSuperclass
Single Table (default)
Joined Table
Table per class
You can read more about them from the official documentation.
They have different pros and cons and normally it is safest to just use the default. However the default strategy only uses one table so you need to switch to an other strategy.
The Table per class will create three tables. You can also check the examples for MappedSuperclass and Joined Table which will also use multiple tables.
From the official documentation:
#Entity(name = "Account")
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public static class Account {
#Id
private Long id;
private String owner;
private BigDecimal balance;
private BigDecimal interestRate;
//Getters and setters are omitted for brevity
}
#Entity(name = "DebitAccount")
public static class DebitAccount extends Account {
private BigDecimal overdraftFee;
//Getters and setters are omitted for brevity
}
#Entity(name = "CreditAccount")
public static class CreditAccount extends Account {
private BigDecimal creditLimit;
//Getters and setters are omitted for brevity
}
Will create these tables:
CREATE TABLE Account (
id BIGINT NOT NULL ,
balance NUMERIC(19, 2) ,
interestRate NUMERIC(19, 2) ,
owner VARCHAR(255) ,
PRIMARY KEY ( id )
)
CREATE TABLE CreditAccount (
id BIGINT NOT NULL ,
balance NUMERIC(19, 2) ,
interestRate NUMERIC(19, 2) ,
owner VARCHAR(255) ,
creditLimit NUMERIC(19, 2) ,
PRIMARY KEY ( id )
)
CREATE TABLE DebitAccount (
id BIGINT NOT NULL ,
balance NUMERIC(19, 2) ,
interestRate NUMERIC(19, 2) ,
owner VARCHAR(255) ,
overdraftFee NUMERIC(19, 2) ,
PRIMARY KEY ( id )
)

Join external column to Hibernate entity using native SQL

I have a (simplified) table structure that looks something like that:
customer table:
id name
-------------------
1 customer1
alias table:
customer_id alias
-------------------------------
1 customer one
1 customer uno
When I run the following query I easily get the list of aliases per customer:
select * from customer_alias where customer_id=1;
I would like to use this query in my hibernate to populate a list of type String. I tried using #Formula as follows:
#Entity
#Table(name = "customer")
public class Customer {
#Id
#Column(name = "id")
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
#Column(name="name")
private String name;
#Formula("(select alias from customer_alias where customer_id = id)")
private List<String> aliases;
// Getters, setters, etc...
}
It didn't work and I got this exception:
Could not determine type for: java.util.List, at table: customer, for columns: [org.hibernate.mapping.Formula( (select alias from customer_alias where customer_id = id) )]
Is there anyway to achieve this? Doesn't have to be with #Formula of course. Any reasonable way would be great.
Here is an SQLFiddle of my example
You could use #ElementCollection for having a List of related aliases without the need to map the whole entity:
#ElementCollection
#CollectionTable(name = "customer_alias", joinColumns = #JoinColumn(name = "customer_id") )
#Column(name = "alias")
private List<String> aliases;
See also:
Difference between #OneToMany and #ElementCollection?
I think you don't want to use OneToMany annotation as the second table is just a list of strings you want to find something more elegant that would not require me to create another entity.
You can use #ElementCollection as below:
#Entity
#Table(name="college")
class College implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="college_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int collegeId;
#Column(name="name")
private String collegeName;
#ElementCollection
#CollectionTable(name="student", joinColumns=#JoinColumn(name="college_id"))
#Column(name="student_name")
private Set<String> students;
public College() {
}
public Set<String> getStudents() {
return students;
}
public void setStudents(Set<String> students) {
this.students = students;
}
public int getCollegeId() {
return collegeId;
}
public void setCollegeId(int collegeId) {
this.collegeId = collegeId;
}
public String getCollegeName() {
return collegeName;
}
public void setCollegeName(String collegeName) {
this.collegeName = collegeName;
}
#Override
public String toString() {
return "College [collegeId=" + collegeId + ", collegeName=" + collegeName + ", students=" + students + "]";
}
}
I don't think #Formula annotation supports collection it can only be applied for single-valued properties. Can't say if if there exists any tweak.

Java Play: Nested models and persistence

I have nested models in a very simple Play application. I have a User model which looks like;
#Entity
public class User extends Model {
#Id
public Integer id;
#Constraints.Email
#Constraints.Required
public String email;
#Constraints.Required
private String password;
#ManyToOne
public City city;
}
And the City model looks like;
#Entity
public class City extends Model {
#Id
public Integer id;
public String name;
#ManyToOne
public Country country;
}
Which is again, very simple.
I then have the Country model, which is;
#Entity
public class Country extends Model {
#Id
public Integer id;
public String name;
}
Now, what I'm doing is POST-ing parameters email, password, and city_id to an action;
public static Result registerUser() {
Form<Register> registerForm = form(Register.class).bindFromRequest();
Logger.debug(registerForm.toString());
if (registerForm.hasErrors()) {
return badRequest(register.render(registerForm));
} else {
User user = form(User.class).bindFromRequest().get();
user.save();
return redirect(controllers.routes.Application.login());
}
}
The database I'm using is MySQL, and I can see the new User rows coming in. What I always see is that city_id stays null which wasn't what I had assumed.
I had assumed Hibernate to take care of the relationship between the objects and the corresponding database foreign keys, but that doesn't seem to be working.
I have a city with id = 1 entered into the city table already and that is the city_id I'm sending through POST.
What's going on here?

How to map one class with multiple tables in Hibernate/javax.persistance?

I want to use one class to map three tables. I know javax.persistance provides the #SecondaryTable annotation to map two tables to one class.
Below is the code, where I have used #SecondaryTable. It allows me to define only one secondary table. But I need 3 tables to be used by the same class.
#Entity
#Table(name = "table1")
#SecondaryTable(name="table2")
public class TableConfig
implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#Column(name = "mac", table= "table1")
private String uniqueIdentifier;
I want to use one class to map three tables, From what I know is that javax.persistance provides #SecondaryTable annotation to map two tables to one class
use #SecondaryTables to map more than one table.
You can map a single entity bean to several tables using the #SecondaryTables class level annotations. To express that a column is in a particular table, use the table parameter of #Column or #JoinColumn.
for example there is 3 entity's namely: Name , Address & Student:
Name entity will look like:
#Entity
#Table(name="name")
public class Name implements Serializable {
#Id
#Column(name="id")
private int id;
#Column(name="name")
private String name;
public Name(){}
public Name(int id,String name){
this.id=id;
this.name=name;
}
//getters and setters
}
Address entity will look like:
#Entity
#Table(name="address")
public class Address implements Serializable {
#Id
#Column(name="id")
private int id;
#Column(name="address")
private String address;
public Address(){}
public Address(int id, String address) {
super();
this.id = id;
this.address = address;
}
//getters and setters
}
Student entity will look like:
#Entity
#Table(name="student")
#SecondaryTables({
#SecondaryTable(name="name", pkJoinColumns={
#PrimaryKeyJoinColumn(name="id", referencedColumnName="student_id") }),
#SecondaryTable(name="address", pkJoinColumns={
#PrimaryKeyJoinColumn(name="id", referencedColumnName="student_id") })
})
public class Student implements Serializable {
#Id
#Column(name="student_id")
private int studentId;
#Column(table="name")
private String name;
#Column(table="address")
private String address;
public Student(){}
public Student(int studentId){
this.studentId=studentId;
}
//getters and setters
}
Store like:
Student s= new Student(1);
session.save(s);
Name n=new Name(s.getStudentId(),"Bilal Hasan");
session.save(n);
Address address = new Address(s.getStudentId(), "India");
session.save(address);
Student ob = (Student)session.get(Student.class, s.getStudentId());
System.out.println(ob.getStudentId());
System.out.println(ob.getName());
System.out.println(ob.getAddress());
ouput:
1
Bilal Hasan
India
you can define one class like below :
#Entity
#Table(name="table1")
#SecondaryTables({
#SecondaryTable(name="table2", pkColumnJoins={#PrimaryKeyJoinColumn(name = "id")}),
#SecondaryTable(name="table3", pkColumnJoins={#PrimaryKeyJoinColumn(name = "id")})
})
public class TestEntity {
#Id
#GeneratedValue
private int id;
private String field1;
#Column(name="column2", table="table2")
private String field2;
#Column(name="column3", table="table3")
private String field3;
getter and setter...
}
In your DB, should has three table, and all of them should has the same primary key "id".
then, use can test like this:
TestEntity test = new TestEntity();
test.setField1("field1");
test.setField2("field2");
test.setField3("field3");
em.merge(test);
after test, in your DB, you will find one record in each table:
table1:
1, field1
table2:
1, field2
table3:
1, field3
all of them will share the primary key value. Hope this will help you.
In Hibernate mapping file you can specify the entity-name mapping with virtual name along with polymorphism="explicit" and class name would be physical class name. Like that you may do multiple mappings. While loading the object use entityname (virtual name).

Bind Stringattribute to Column of separate Table

is it possible to bind a String attribute of an JavaClass to a column of a different table.
Example
#Entity
#Table(name = "ACCOUNTS")
public class Account {
private Long id;
private String nickname;
private String address;
#Id
#Column(name = "A_ID")
#GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name="A_NICKNAME")
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
What would be the correct annotation if the address where a String in a separate table?
Separate Table
CREATE TABLE IF NOT EXISTS `Adresses` (
`ad_id` BIGINT NOT NULL AUTO_INCREMENT ,
`ad_address` VARCHAR(45) NOT NULL ,
PRIMARY KEY (`da_id`) )
ENGINE = InnoDB;
thanks
You can't define address field of your object to be stored in another table (it has its own identifier that you don't have it in your account class ) but you can define another class for address and let it have its own mapping and table then your account class will have a many to one relationship with address class.This approach can have advantages too.What if you want to add another fields to your address table ? and if there's no chance that other fields can be added to your address table why do you want to store it in a separate table ?

Categories

Resources