JavaEE 1:1 Unidirectional Relationship - mysql

I am confused about how the JPA handles a 1:1 unidirectional relationship when I auto generate my tables from my entity classes vs. how I would make those tables if I was creating the SQL tables myself.
My question concerns how the foreign keys are set up.
Assuming I have a Customer entity and an Address entity like this:
#Entity
public class Customer{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(cascade = CascadeType.ALL)
private Address address;
//setters and getters
}
#Entity
public class Address{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String streetName;
//setters and getters
}
If I generate the sql tables from these two entity classes then the Customer table will have a foreign key column referencing the Address table.
Is there a way to have a OneToOne unidirectional relationship where the JPA sticks the foreign key on the target table(the address table) instead of on the Customer table as it does in my code example, or do I have to make it a bidirectional relationship and show more explicit ownership with the #Mapping annotation to achieve this kind of table structure?

To have the foreign key appear on the other table for a 1-1 unidirectional relationship you would need to switch the 'owning' side: place customer in Address and remove address from Customer. Then the foreign key for customer would be in the address table.

First of all, you have to map your address_id(or something like that) from the Customer entity. For example using below code, you are referencing the Address from the Customer.
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="ADDRESS_ID")
private Address address;
While deleting any entry from the address table, you must first delete the entry from the Customer table in order to avoid the foreign key violation error. Then you can delete from Address table. JPA doesn't handle this relationship while deleting any row from table.

Related

Tables sharing id column in springboot with mysql

I hava a spring-boot application that makes CRUD operations on a mysql datebase.
In thid db, there are two tables 'Employee' and 'Employee_departament' (both with auto-increment primary key and employee_id is foreign key in Employee_departament).
When I add a new employee in Employee table it will generate a record with id = 1. After that, if I add a new record in Employee_departament, this one will have id = 2.
This looks like my tables are sharing id column.
If you are using #GeneratedValue(strategy = GenerationType.SEQUENCE) above the id fields in both of your entities then they will both use the default database sequence (named hibernate_sequence) to generate new ids. This results in the behavior that you are describing.
If you want the entities to have their ids generated from separate sequences, you can specify the name of the sequence to get the ids by specifying the generator value inside of the GeneratedValue annotation. For example:
Employee.java
#Entity
#Table(name = "employee")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "employee_seq")
private Long employee_id;
...
EmployeeDepartment.java
#Entity
#Table(name = "employee_department")
public class EmployeeDepartment {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "employee_department_seq")
private Long employee_department_id;
...
Please add these two annotations into your entity class id attribute
#GeneratedValue(strategy = GenerationType.AUTO)
#Access(AccessType.PROPERTY)

Why #JoinTable(name="user_role") not allowing multiple user_id with role_id

Am using springboot with hibernate,
My Entity classes looks like below :
#Entity
#Table(name="tbl_user")
public class User {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="user_Id")
private long userId;
#Column(name="userName")
private String userName;
#Column(name="passWord")
private String passWord;
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name="user_role")
private Collection<Role> roleList;
My second entity looks like below :
#Entity
#Table(name="tbl_role")
public class Role {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="role_Id")
private long roleId;
#Column(name="roleName")
private String roleName;
When I insert first user with role as manager(pkid=1), admin(pkid=2) its success but while I tried to insert 2nd user with role as Manager*pkid=1, admin(pkid=2, serviceUser(pkid=3) it's not allowing me to insert second user with below exception
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '2' for key 'UK_ixctfj5iq0enl7iktlpo7wxct'
Can somebody help me why this constraint is getting creating while generating tables, how can i insert 2nd user into DB ?
If you use OnetoMany on role_list you are effectively saying that a single User will point to many Roles and that a Role will point to only one User. This will be enforced with a unique key constraint placed on the join table. If you have the SQL statements printed out you will see it when the schema is created. Something along the lines of:
alter table user_role add constraint UK_ixctfj5iq0enl7iktlpo7wxct unique (role_id)
In your requirement, you also have a single Role used by many Users. Your admin role primary key is 2 and you want to be able to assign it to more than one user. Your relationship is a ManyToMany for the role_list.
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="user_role")
private Collection<Role> roleList;
When you change the annotation, you will still have a join table, but no constraint will be added.

Bidirectional mapping of entities in Hibernate and spring mvc

I am trying to map two entities User(userid,name,password,address) and Role(roleId,roleName).
I am trying to do bidirectional OneToMany mapping between User and Role.
My User entity:
#OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#JoinTable(
name="UserRole",
joinColumns = #JoinColumn(name="USER_ID"),
inverseJoinColumns = #JoinColumn(name="ROLE_ID")
)
public Set<Role> role;
My Role entity:
#ManyToOne(cascade = CascadeType.ALL)
#JoinTable(
name="UserRole",
joinColumns= #JoinColumn(name="ROLE_ID")
)
private User user;
Now the corresponding intermediate table created in the database has following attributes.
userrole-> attributes( user_userId,ROLE_ID,USER_ID )
Now when i add set of items to a user. ROLE_ID and USER_ID of userrole table gets populated but user_userId remain null. What is the purpose of user_useerId.Should i manully make it primary key in the table user_userId?
UPDATE:
i did the following editing in Role entity
#ManyToOne(cascade = CascadeType.ALL)
#JoinTable(
name="UserRole",
joinColumns = #JoinColumn(name = "ROLE_ID"),
inverseJoinColumns = #JoinColumn(name="USER_ID")
)
private User user;
And now when i checked the table in the database, the "userrole" table is perfectly ok and contains only (USER_ID,ROLE_ID)
I want to know why shouldn't i map two entities though this way?
In User entity you have declared #OneToMany with Role and also given details about the Join table using #JoinTable.
So if you need bidirectional between User and Role entities, then adding user property in Role entity with #ManyToOne declaration is sufficient, so no need of having #JoinTable once again.
Now coming to user_userId - in your Role entity you have declared the #JoinTable annotation and this annotation needs two column names for Join Table.
#JoinTable(
name="JoinTableName",
joinColumns = #JoinColumn(name="ID_NAME_FOR_THIS_ENTITY"),
inverseJoinColumns = #JoinColumn(name="ID_NAME_FOR_THE_MAPPING_ENTITY")
)
but you have provided one with name ROLE_ID and ignore the second name which is inverseJoinColumns that points to User entity.
#JoinTable(
name="UserRole",
joinColumns= #JoinColumn(name="ROLE_ID")
)
so hibernate should make a guess about the column name for inverseJoinColumns, so it creates a column with name as combination of the entity name in lower case (which is user in your case) then the identifier in that entity separated by underscore (which is userId I guess based on column name). So finally the column name becomes user_userId.
Update:
If you need bidirectional relationship then you need to declare your entities like this:
#OneToMany(mappedBy="user", cascade=CascadeType.ALL,fetch=FetchType.EAGER)
public Set<Role> role;
Here you are telling to hibernate that User entity has one-to-many relationship with Role entity and the relationship is bi-directional and the Role entity has a property called user.
Now in your Role entity you will give details about the JoinTable like this:
#ManyToOne(cascade = CascadeType.ALL)
#JoinTable(
name="UserRole",
joinColumns = #JoinColumn(name="ROLE_ID"),
inverseJoinColumns = #JoinColumn(name="USER_ID")
)
private User user;
The property name user in Role entity should match with the mappedBy attribute that you have declared in your User entity for the #OneToMany annotation. Adding the user field to Role entity makes the relationship bi-directional.
A (typical) bidirectional mapping has one side that is maintaining the relationship and the other side follows this relation ship.
This mean when you modifiy the maintaining side of the relation ship and save this change, then it gets stored in the database. While when you modifiy the following side, then nothing gets changed in the database. (it gets only updated when you load the entity).
A mapping would looks like:
#Entity
public class User {
...
#OneToMany(mappedBy="user") //mappedBy makes this side to the following side
public Set<Role> role;
...
}
#Entity
public class Role {
...
#ManyToOne
//#JoinTable... when you need it
private User user;
...
}
But this mapping is strange: because it mean that every User can have serveral roles, but each role belongs to exactly one user. -- If this is not intended, then switch ManyToOne and OneToMany or move over to ManyToMany

How do I auto-generate IDs for an #ElementCollection when it is a java.util.Map?

I’m using MySQL 5.5.37, JPA 2.0, and Hibernate 4.1.0.Final (I’m willing to upgrade if it solves my problem). I have the following entity …
#Entity
#Table(name = "url")
public class Url implements Serializable
{
…
#ElementCollection(fetch=FetchType.EAGER)
#MapKeyColumn(name="property_name")
#Column(name="property_value")
#CollectionTable(name="url_property", joinColumns=#JoinColumn(name="url_id"))
private Map<String,String> properties;
The “url_property” table has an ID (primary key) column, and perhaps for this reason, when I create a new Url entity with multiple properties, I feet the exception
[ERROR]: org.hibernate.engine.jdbc.spi.SqlExceptionHelper - Duplicate entry '' for key 'PRIMARY'
upon saving. Does anyone know what I have to do to auto-generate IDs for my url_property table? I would prefer not to write a trigger, but rather do something JPA, or at least, Hibernate sanctioned.
Edit: Per the first suggestion in the answer, I tried
#ElementCollection(fetch=FetchType.EAGER)
#Column(name="property_value")
#CollectionTable(name="url_property", joinColumns=#JoinColumn(name="url_id"))
private Set<UrlProperty> properties;
but it resulted in the exception, "org.hibernate.MappingException: Foreign key (FK24E4A95BB0648B:url_property [properties_id])) must have same number of columns as the referenced primary key (url_property [url_id,properties_id])".
My UrlProperty entity is
#Entity
#Table(name = "url_property")
public class UrlProperty
{
#Id
#GeneratedValue(generator = "uuid-strategy")
private String id;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name="URL_ID")
private SubdomainUrl url;
#Column(name="PROPERTY_NAME")
private String propertyName;
#Column(name="PROPERTY_VALUE")
private String propertyValue;
You have only told JPA about 3 fields in the table ("property_name","property_value" and "url_id"), so it has no way of knowing about the 4th field used as the pk. Since it is not an entity, it doesn't have an Identity that is maintained. Options are:
1) Map the "url_property" table to a Property entity, which would have an ID, value and reference to the Url. The Url would then have a 1:M reference to the Property class, and can still be keyed on the name. http://wiki.eclipse.org/EclipseLink/Examples/JPA/2.0/MapKeyColumns has an example
2) Change your table to remove the ID field, and instead use "property_name","property_value" and "url_id" as the primary key.
3) Set a trigger to populate the ID. Doesn't seem useful though since the application is never aware of the field anyway.

JPA doesn't t allow entity made of columns from multiple tables?

I know this makes none sense as many tutorials state that you can use SecondaryTable annotation, however it doesn't work in hibernate. I have schema like this:
#Entity
#Table(name="server")
#SecondaryTable(name="cluster", pkJoinColumns = { #PrimaryKeyJoinColumn(name = "uuid", referencedColumnName = "cluster_uuid") })
public class Server {
#Id
#Column(name = "uuid")
private String uuid;
#Column(name = "cluster_uuid")
private String clusterUuid;
#Column(name = "ip", table="cluster")
private String ip;
..... }
#Entity
#Table(name = "cluster")
public class Cluster {
#Id
#Column(name = "uuid")
private String uuid;
#Column(name = "ip")
private String ip;
.....
}
Server.clusterUuid is a foreign key to Cluster.uuid. I am hoping to get Server entity that fetches ip column from Cluster by joining Server.clusterUuid to Cluster.uuid.
Then I was greeted by a hibernate exception:
Caused by: org.hibernate.AnnotationException: SecondaryTable
JoinColumn cannot reference a non primary key
at org.hibernate.cfg.annotations.TableBinder.bindFk(TableBinder.java:402)
at org.hibernate.cfg.annotations.EntityBinder.bindJoinToPersistentClass(EntityBinder.java:620)
at org.hibernate.cfg.annotations.EntityBinder.createPrimaryColumnsToSecondaryTable(EntityBinder.java:612)
I see lots of people encountered this problem. But the first bug for this in Hibernate's bugzilla was 2010, I am surprised it's been there for over two years as this is supposed to be a basic feature. There is some post saying JPA spec only allows primary key to do the mapping, however, I get below from JPA wikibook
JPA allows multiple tables to be assigned to a single class. The
#SecondaryTable and SecondaryTables annotations or
elements can be used. By default the #Id column(s) are assumed to be
in both tables, such that the secondary table's #Id column(s) are the
primary key of the table and a foreign key to the first table. If
the first table's #Id column(s) are not named the same the
#PrimaryKeyJoinColumn or can be used to
define the foreign key join condition.
it's obviously OK for non-primary key. Then I am confused why Hibernate didn't fix this problem as it seems to be easy to implement by a join clause.
anybody knows how to overcome this problem? thank you.
I don't quite understand your setup.
#SecondaryTable is for storing a single entity in multiple tables, but in your case you have a many-to-one relationship between different entities (each one stored in its own table), and it should be mapped as such:
#Entity
#Table(name="server")
public class Server {
#ManyToOne
#JoinColumn(name = "cluster_uuid")
private Cluster cluster;
...
}