I want to map two tables (ManyToOne connection) to one object in Java. One is primary CatalogObject table, the second is just a dictionary of possible types of objects. In Java I want to just have the String of type instead of mapping to a new object.
When I want to search for all objects in the class (f.e. "database") i have to first find an Id of type "database" and then find all CatalogObjects with this Id specified. Which looks a bit tedious.
CatalogObject Table:
ID, Name, Parent_ID (FK), TYPE_ID (FK)
Type Table:
ID, Type.
I've created a mapping with CatalogObject and CatalogObjectType classes, but CatalogObjectType class holds only single String right now.
public class CatalogObject implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
CatalogObject parent;
String name;
#ManyToOne
#JoinColumn(name = "type_id")
CatalogObjectType type;
}
public class CatalogObjectType implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
}
I want to replace CatalogObjectType with just a String value of the associated type. How to configure it for Hibernate/JPA? Can it be done?
What I want is:
public class CatalogObject implements Serializable {
...
String name;
#SomeAnnotation(name = "type_id")
String type;
}
Yes, you can map an entity to 2 database tables in 2 simple steps:
You need to annotate your entity with JPA’s #Table and #SecondaryTable annotations and provide the names of the first and second table as the value of the name parameters.
You need to annotate each attribute which you want to map to the secondary table with a #Column annotation and set the name of the secondary table as the value of the table attribute.
The #Table annotation defines the primary table to which the entity attributes get mapped by default.
The #SecondaryTable annotation specifies the second database table to which the entity gets mapped.
That’s all you need to do to map the 2 database tables to the one entity.
You can check this link for a detailed explanation with a sample.
Related
i have the empty database in mysql, and two java entites. One of those have unidirectional relation. When hibernate tryes to create tables, i got the error:
Error executing DDL "alter table entry add constraint FK6ov2k83sx3crs9v3q8nvjuf1j foreign key (category_name) references category (name)" via JDBC Statement
There are my entites:
#Entity
public class Entry {
#Id
#GeneratedValue( strategy = GenerationType.IDENTITY)
private int id;
#Column
private String myfio;
private String descr;
#OneToOne(cascade = CascadeType.ALL)
private Category category;
}
And the second:
#Entity
#Table(name="category")
public class Category {
#Id
#Column
private String name;
}
How to create tables without errors?
OneToOne relationship shares the same id. So it should be the same type, but the first one is int (actually it should be Integer to allow null value for the transient (not stored) entities) and the second one is String. It seems you simply missed a line. Also, it worths to mention Vlad Mihalchea’s article https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
TLDR;
I'm using spring boot and jpa.
I want to switch the foreign key of an object, in this case just switching the category of a vehicle.
But when i try to do that hibernate interprets it as if i'm trying to change the primary key of the category object instead of just switching the foreign key and I get this error
org.hibernate.HibernateException:identifier of an instance of abc.package.mode.Category was altered from 1 to 2
I have an entity Category which i'm using only for categorizing vehicle entity object.
#Entity
public class Category {
#Id
private Long id;
private String name;
}
Here is the Vehicle class which needs to be categorized.
#Entity
public class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator="dish_seq")
private Long id;
private String name;
private Integer price;
#ManyToOne(fetch = FetchType.EAGER, cascade=CascadeType.DETACH)
private Category category;
}
Lets say there's 3 categories,
'Sedan'
'Convertible'
'Hatchback'
If i have a car object,
Nissan-PT76, $30000, category: [id:1, name:Sedan]
When i try to change category manually to [id:2, name:Convertible] and persist it, i get
org.hibernate.HibernateException:identifier of an instance of abc.package.mode.Category was altered from 1 to 2
I cannot switch from one existing object to another. I have tried to look this up in the internet but i couldn't find the right keywords to search for this kind of relationship in hibernate, or does it not allow this kind of relationship at all?
Add column reference to your Category field in the Vehicle class
#JoinColumn(name = "category_id", nullable = false)
I have a problem with my Hibernate making assumptions on what to call columns.
Specifically, when I do a #ManyToOne field, where I refer to a column in the other Table.
What happens is that, If I do not enter a #JoinColumn annotation as well, it maps the field with an underscore in it's name.
For example, i have this class:
#Entity
public class User extends AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "userFK")
private List<TwitterAccount> twitterAccounts;
/* GETTERS & SETTERS OMITTED */
}
And then I have the TwitterAccount class:
#Entity
public class TwitterAccount extends AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
private User userFK;
}
What happens is that when it tries to get the Twitter accounts for a certain user, I get the exception: MySQLSyntaxErrorException: Unknown column 'twitteracc1_.userFK_id' in 'field list'
Look at what it tries to map the userFK to: userFK_id. Of course it doesn't exist! I haven't given it that name.
So the question comes down to: Is it possible to turn off this functionality? (The functionality that turns the column name into 'field_'foreignkey')
I am aware that using #JoinColumn(name = "userFK") would solve it, but I'd rather turn it off instead.
Regards
This is the default as specified by the JPA specification
The concatenation
of the following: the name
of the referencing relationship
property or field of the referencing
entity or embeddable class;
""; the name of the referenced
primary key column. If there is no
such referencing relationship
property or field in the entity, or if
the join is for an element collection,
the join column name is
formed as the concatenation of the
following: the name of the entity;
""; the name of the referenced
primary key column.
In hibernate this is implemented in a NamingStrategy in this case the EJB3NamingStrategy. You can implement your own version of this deciding whatever you want to use. But that will probably only complicate/confuse people (which might expect the standards to apply).
i am trying on many to many relationship, Team member can work on multiple projects and a project can have multiple team member , the table structure is as follows,
create table TBL_PROJECT_ONE(
id integer primary key generated always as identity(start with 12,increment by 3),
name varchar(50)
)
create table TBL_TEAM_MEMBER_ONE(
id integer primary key generated always as identity(start with 7,increment by 5),
name varchar(50),
salary integer
)
create table EMP_PRJ_CADRE(
MEMBER_ID integer references TBL_TEAM_MEMBER_ONE,
PRJ_ID integer references TBL_PROJECT_ONE,
CADRE varchar(10),
constraint PK_001_EMP_TEAM primary key (MEMBER_ID,PRJ_ID)
)
Here i have created a new table just to store the relationship,
Now please follow the Employee entity,
#Entity
#Table(name="TBL_TEAM_MEMBER_ONE")
public class EmployeeEntityFour implements Serializable{
public EmployeeEntityFour(){}
public EmployeeEntityFour(String empName,Integer salary){
...
..
}
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="ID")
private Integer empId;
#Column(name="NAME")
private String empName;
#Column(name="SALARY")
private Integer empSal;
#ElementCollection(fetch= FetchType.LAZY)
#CollectionTable(name="EMP_PRJ_CADRE")
#MapKeyJoinColumn(name="PRJ_ID")
#Column(name="CADRE")
private Map<ProjectEntityOne,String> employeeCadre;
...
..
.
}
Please follow the mapping for Project Entity,
#Entity
#Table(name="TBL_PROJECT_ONE")
public class ProjectEntityOne implements Serializable{
public ProjectEntityOne(){}
public ProjectEntityOne(String name){
this.projectName = name;
}
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="ID")
private Integer projectId;
#Column(name="NAME")
private String projectName;
#ElementCollection(fetch= FetchType.LAZY)
#CollectionTable(name="EMP_PRJ_CADRE")
#MapKeyJoinColumn(name="MEMBER_ID")
#Column(name="CADRE")
private Map<EmployeeEntityFour,String> employeeCadre;
....
..
.
}
In main method testing the code written is as follows,
ProjectEntityOne proj = new ProjectEntityOne("Citi Grand Central");
Map<EmployeeEntityFour,String> cadreMap = new HashMap<EmployeeEntityFour,String>();
cadreMap.put(new EmployeeEntityFour("Murlinarayan Muthu",34000), "Senior Software Engineer");
cadreMap.put(new EmployeeEntityFour("Gopalkrishna Rajnathan",64000), "Software Engineer");
cadreMap.put(new EmployeeEntityFour("Premanna Swaminathan",94000), "Project Manager");
proj.setEmployeeCadre(cadreMap);
em.persist(proj);
but i am getting an error which is
ERROR: 'PROJECTENTITYONE_ID' is not a column in table or VTI 'APP.EMP_PRJ_CADRE'.
When in both the entities i have specified #MapKeyJoinColumn than too i am getting an error as improper column for the third table.
Where i am missing
It somehow worked, i had to do some changes in the code,
first, the edited code in Entity ProjectEntityOne is as follows,
#ElementCollection(fetch= FetchType.LAZY)
#CollectionTable(name="EMP_PRJ_CADRE",joinColumns=#JoinColumn(name="PRJ_ID"))
#MapKeyJoinColumn(name="MEMBER_ID")
#Column(name="CADRE")
private Map<EmployeeEntityFour,String> employeeCadre;
What i have done here is i added #JoinedColumn in #CollectionTable,
Second change i did in Entity EmployeeEntityFour, the change is I removed Map of PorjectEntityOne from it,
in test,
i can save Project with Employee mapping but here all the employees should be already saved one.
i.e. the key of map
Map<EmployeeEntityFour,String> employeeCadre;
should be already persisted
and than we can persist project entity.
On employeeCadre in EmployeeEntityFour you need a #JoinColumn(name="MEMBER_ID") and you would also need a #JoinColumn(name="PRJ_ID") in the ProjectEntityOne employeeCadre.
But, I would not model it this way. First of all you cannot have a bi-directional ElementCollection mapping, and ElementCollection can only be owned by one side. The best solution would be to define an Cadre entity mapping to EMP_PRJ_CADRE table and have a OneToMany to it from both sides, and have it have a ManyToOne to each.
Alternatively you may use a ManyToMany with a MapKeyColumn, but I think you would be better off having an entity.
I have four entities to map together, "Association", "Account", "Transaction" and "TransactionEvent". The id of Association is a simple integer id. Account and Transaction each have embedded id's consisting of a mapping to an Association and a number.
TransactionEvent should have an embedded id consisting of one Account and one Association. Now, each of those are mapped to an Association, and I want it to be the same Association for one TransactionEvent.
JPA Annotations is used for the Hibernate mapping, but I cannot make this work. I have tried forcing the same column name for the Association key, but Hibernate complains about repeated columns.
Is this possible to solve, or am I not thinking straight?
Here are the annotated classes, but I trimmed away getters/setters and non-id columns, annotations from the javax.persistence namespace:
#Entity
public class Association implements Serializable {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id;
}
#Embeddable
public class AccountPK implements Serializable {
#ManyToOne(optional=false)
private Association association;
#Column(nullable=false)
private int number;
}
#Embeddable
public class TransactionPK implements Serializable {
#ManyToOne
private Association association;
#GeneratedValue(strategy=GenerationType.AUTO)
private long number;
}
#Embeddable
public class AccountEventPK implements Serializable {
#ManyToOne(optional=false)
#JoinColumns({
#JoinColumn(name="association_id", referencedColumnName="association_id"),
#JoinColumn(name="account_number", referencedColumnName="number")
})
private Account account;
#ManyToOne(optional=false)
#JoinColumns({
#JoinColumn(name="association_id", referencedColumnName="association_id"),
#JoinColumn(name="transaction_number", referencedColumnName="number")
})
private Transaction transaction;
}
Actual Account, Transaction and AccountEvent entities are on the form
#Entity
public class Account implements Serializable {
#EmbeddedId
private AccountPK id;
}
I don't have much experience with placing associations directly in the embedded id component since this is not supported by JPA but is Hibernate specific.
As an alternative my suggestion would be to use the approach described in the Composite Primary Keys section of the JPA wikibook:
(...) JPA 1.0 requires that all #Id
mappings be Basic mappings, so if
your Id comes from a foreign key
column through a OneToOne or
ManyToOne mapping, you must also
define a Basic #Id mapping for the
foreign key column. The reason for
this is in part that the Id must be a
simple object for identity and caching
purposes, and for use in the IdClass
or the EntityManager find() API.
Because you now have two mappings for
the same foreign key column you must
define which one will be written to
the database (it must be the Basic
one), so the OneToOne or ManyToOne
foreign key must be defined to be
read-only. This is done through
setting the JoinColumn attributes
insertable and updatable to false,
or by using the
#PrimaryKeyJoinColumn instead of the
#JoinColumn.
A side effect of having two mappings
for the same column is that you now
have to keep the two in synch. This is
typically done through having the set
method for the OneToOne attribute
also set the Basic attribute value to
the target object's id. This can
become very complicated if the target
object's primary key is a
GeneratedValue, in this case you
must ensure that the target object's
id has been assigned before relating
the two objects.
(...)
Example ManyToOne id annotation
...
#Entity
#IdClass(PhonePK.class)
public class Phone {
#Id
#Column(name="OWNER_ID")
private long ownerId;
#Id
private String type;
#ManyToOne
#PrimaryKeyJoinColumn(name="OWNER_ID", referencedColumnName="EMP_ID")
private Employee owner;
...
public void setOwner(Employee owner) {
this.owner = owner;
this.ownerId = owner.getId();
}
...
}
This looks like to be what you're looking for (and maybe less complicated). I'd try to implement this solution (incrementally).