Strange behaviour of spring data jpa - java

I'm new to JPA and I have a case where in my opinion JoinColumn behaves different and I want to know why.
UserEntites should join authorites.
Organziations should join OrganizationSettings.
I have two different approaches and both work.
Case 1
UserEntity :
#Entity
#Table(name = "users")
#Inheritance(strategy = InheritanceType.JOINED)
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "userId")
private List<UserAuthority> authorities;
}
UserAuthoritiesEntity
#Entity(name = "authorities")
#Table(name = "authorities")
public class UserAuthority {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private String authority;
}
Here in my opinion the JoinColumn name references to UserAuthority.userId - and it works as expected.
Case 2
See my two other classes:
OrganizationEntity:
#Entity
#Table(name="organization")
public class OrganizationEntity {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#NotNull
private String url;
#NotNull
private String name;
#OneToOne (cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name="id",updatable = false)
private OrganizationSettingsEntity settings;
}
OrganizationSettings:
#Entity
#Table(name = "organization_settings")
public class OrganizationSettingsEntity {
#Id
private Long organizationId;
}
As you can see here -> Organization is Joining OrganizationSettings with the name id - which works. But in OrganizationSettings there is no id - just organizationId. This works - but makes me wonder.
Why does the second one also work? Shouldn't it be #JoinColumn(name="organizationId") ?

Spring is nothing to do with it. JPA is a standard API.
1-N case : you will create a FK column in the authorities table with name userId (linking back to the users table). You seem to also want to REUSE that same column for this userId field in the element ... this will cause you problems sooner or later since reusing columns without marking the userId field as insertable=false, updatable=false will mean that both may try to update it. Either get rid of the userId field in the element, or convert the field to be of type UserEntity (and have it as a bidirectional relation, using mappedBy on the 1-N owner field), or mark the userId field with those attributes mentioned earlier.
1-1 case : you will create a FK column in the organization table with name id (linking across to the organization_settings table). Sadly this is the same column as the PK of that table is going to use, so again you are reusing the column for 2 distinct purposes, and hell will result. Change the column of the relation FK to something distinct - the FK is in the organization table, not the other side.

Related

Spring boot Jpa Entity, map same referenced column as id and entity

I have two tables user and student. Student and user has one to one relationship.
I am mapping same column with both userId and UserEntity inside StudentEntity
#Entity
#Table(name = "user")
public class User {
#Id
private UUID id;
#Column(name = "name")
private String name;
...
// getter setter ignored for brevity
}
#Entity
#Table(name = "student")
public class Student {
#Id
private UUID id;
#Column(name = "user_id")
private UUID userId;
#OneToOne
#JoinColumn(name = "user_id", referencedColumnName = "id",
insertable = false, updatable = false)
private UserEntity userEntity;
...
// getter setter ignored for brevity
}
#Repository
public interface StudentRepository extend PagingAndSortingRepository<StudentEntity, UUID> {
Optional<StudentEntity> findByUserId(UUID userId);
}
Now when I call any of below method UserEntity is not populated
StudentEntity student = studentRepository.findByUserId(userId).get()
student.getUserId() --- this is present
student.getUserEntity() -- NULL
Not sure why UserEntity is not getting populated as a part of this call.
The issue is most likely that your persistence context still contains the entity that you previously saved (which has no user set). You would first have to clear the persistence context and the load it again to see a non-null user.
Anyway, like suggested in the comments, I would advise you to just map the user. You don't need the userId field. The way you mapped it now, you should be able to use the following:
Optional<StudentEntity> findByUserEntityId(UUID userId);
Also read the official documentation for more information on the matter: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-property-expressions
You can try reverse mapping mapping like below by using mappedby in the owner of the relationship
jpa hibernate #OneToOne #JoinColumn referencedColumnName ignored

Hibernate - In a one-to-many relationship, a child loses references to the parent when updating

As in the title, when performing the update operation, the previous child loses the reference to the parent.
Parent side
#OneToMany(cascade =CascadeType.ALL)
#JoinColumn(name = "individual_id")
private List<ContactMedium> contactMedium;
Children side
#Entity
#Table(name = "contactMedium")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ContactMedium
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id ;
#ManyToOne
private Individual individual;
Patch operation
public Individual patch(Individual individual, Long id) {
Individual objectToSave = individual;
objectToSave.setId(id);
return individualRepository.save(objectToSave);
}
When updating, the previous property loses references to the child. How can I prevent this?
Your mappings seems wrong. Ideally they should be as below:
#Entity
#Table(name = "contactMedium")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ContactMedium
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id ;
#ManyToOne
#JoinColumn
private Individual individual;
and
#OneToMany(mappedBy = "individual", cascade = CascadeType.ALL)
private List<ContactMedium> contactMedium;
You need to save the ContactMedium and Individual will automatically be saved. Here ContactMedium has the foreign key reference to Individual (and that is what is depicted in your database table screenshot).
Often one use mappedBy as parameter to #OneToMany instead of #JoinColumn to make the relationship two-ways.
Can you please try to change
#OneToMany(cascade =CascadeType.ALL)
#JoinColumn(name = "individual_id")
private List<ContactMedium> contactMedium;
to
#OneToMany(mappedBy = "individual", cascade =CascadeType.ALL)
private List<ContactMedium> contactMedium;
and see if that worked better?
I think you must add the #OneToMany(mappedBy="individual" , cascade =CascadeType.PERSIST) and the #JoinColumn in the #ManyToOne as below:
#OneToMany(mappedBy = "individual", cascade =CascadeType.PERSIST)
private List<ContactMedium> contactMedium;
#ManyToOne
#JoinColumn(name = "individual_id")
private Individual individual;
You should retrieve the entity from the database using the ID first and then update the specific fields and persist the updated entity back.

#PrimaryKeyJoinColumn not picking the shared key

I got the #PrimaryKeyJoinColumn working before , now I'm trying with spring boot and I can't figure what I'm missing , it's very weird as it seems I have done everything right :
Person class :
#Table(name = "PERSON")
#Entity
#Getter
#Setter
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.PERSIST)
#PrimaryKeyJoinColumn
private Department department;
}
Department class :
#Table(name = "DEPARTMENT")
#Entity
#Getter
#Setter
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
#OneToOne(mappedBy = "department")
private Person person;
}
The service class :
#Transactional
public void addPerson() {
Person person = new Person();
person.setName("person 1");
Department department = new Department();
department.setName("test");
department.setPerson(person);
person.setDepartment(department);
personRepository.save(person);
}
That's what I get in the DB :
Person table :
ID Name
13 person 1
Department table :
ID Name
18 test
Desired output : both ID should be identical ( Person ID should be 18 not 13)
any idea ?
Update:
hibernate always tries to insert Person without an ID , so if I remove auto increment from Person ID and tries to insert the person with existing department I get :
Hibernate: insert into person (name) values (?)
Field 'id' doesn't have a default value
Update 2:
it seems the #PrimaryKeyJoinColumn won't handle the ID generation to be as the department class, so I need to use generator. I wonder because the same annotation works in Inheritance joined without doing anything on the ID of subclass. So I'm expecting an answer explaining why ID generation works in Inheritance joined , while OneToOne needs a generator
Although it is at least bad naming to make whole Department for just ONE Person (Department usually groups many people), I assume you really are looking for OneToOne relation with shared PRIMARY KEY.
The best solution (optimal memory, speed and maintenance) is to use #MapsId:
#Getter
#Setter
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToOne(mappedBy = "person", fetch = FetchType.LAZY)
private Department department;
}
#Getter
#Setter
#Entity
public class Department {
#Id // Note no generation
private Long id;
private String name;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
#JoinColumn(name = "id", foreignKey = #ForeignKey(name = "department_belongs_to_person"))
private Person person;
}
Here Department is owning relation (owning usually means that it would have column with FK in database, but here they just share primary key) and owns FK that binds it's PK to one generated by Person.
Note that relation is OneToOne, bidirectional, with shared PK as FK. This is considered best practice, but has one small drawback of having to write one getter. :)
Sources: https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
Also - I highly recommend spending few days reading posts on this site and even implement few of them before designing anything bigger than few tables. :)
EDIT
I might be wrong here (I am sure now - look comments), but to my knowledge keeping IDs equal is not something JPA specifies. What it can specify is basically:
"Hey, you guys (Person, Department) are brothers (OneToOne) and both know it (bidirectional with mappedBy="person" in Person entity). You will be older bro that will generate IDs (Person has #GeneratedValue), and you will be youger that should have same. You will use those fields (IDs, one generated, second not) to connect (#PrimaryKeyJoinColumn)."
What I am trying to say is that just because you say "this connects you", it doesn't mean those are synchronized - YOU have to ensure it.
Now as to how to ensure it - #MapsId is known to be best.
If you are looking for different approaches there is also manually setting ID to be same as other with #setDepartment(Department) where you would set Department's ID to be same as calling Person (but this only works if said Person already has an ID generated, which basically breaks the idea).
The other known to me is using foreign generator strategy.
Person:
#Id
#GeneratedValue
private long id;
#OneToOne(mappedBy="person")
private Department department;
Department:
#Id
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters=#Parameter(name="property", value="person"))
private long id;
#OneToOne
#PrimaryKeyJoinColumn
private Person person;
Now - this uses hibernate-specific annotations and is not pure JPA.

Hibernate mapping join on one column of constants table

I have 2 tables, the first one is quite variable, the second one contains only constants:
USER.ID USER.NAME USER.USER_TYPE (FK on USER_TYPE.ID)
INT VARCHAR(64) INT(1)
----------------------------------
1 Alex 3
2 Jane 1
3 Carl 3
USER_TYPE.ID USER_TYPE.VALUE
INT(1) VARCHAR(64)
------------------------------
1 PENDING
2 REGISTERED
3 BANNED
4 ACTIVE
The foreign key USER.USER_TYPE is required and refering to a primary key USER_TYPE.ID in table USER_TYPE (one-to-one relation). Here is my mapping in Hibernate.
User.java
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_TYPE")
private UserType userType;
}
UserType.java
#Entity
#Table(name = "USER_TYPE")
public class UserType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "VALUE")
private String value;
}
My goal is to keep the enumerated values in the database. How to map UserType's value instead of id to User and validate it? I want to pass the constant VALUE to the String instead of its ID.
private String userType;
The expected result of the first user would be:
User[id=1, name=Alex, userType=Banned]
User[id=2, name=Jane, userType=Pending]
User[id=3, name=Carl, userType=Banned]
My attempt was to use this annotation on definition of table twice with both colums switched
#SecondaryTable(name="USER_TYPE",
pkJoinColumns={#PrimaryKeyJoinColumn(name="ID", referencedColumnName="USER_TYPE")}
)
and get the VALUE with
#Column(table="USER_TYPE", name="VALUE")
private String UserType;
however it leads to the error
Unable to find column with logical name: USER_TYPE in org.hibernate.mapping.Table(USER) and its related supertables and secondary tables
First you need to change the relation from #OneToOne to #ManyToOne as UserType can be used by one or many User and User can have one and one UserType.
Secondly use referencedColumnName which references :
The name of the column referenced by this foreign key column.
So User entity will be:
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_TYPE", referencedColumnName = "VALUE")
private UserType userType;
}
In UserType you should apply a unique constraint using #NaturalId to value field + do not provide its setter, to prevent duplicate values as It may lead to inconsistency:
#Entity
#Table(name = "USER_TYPE")
public class UserType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#NaturalId
#Column(name = "VALUE")
private String value;
}
Hope it solves the issue!
Enumerations could be simpler:
enum UserType {
PENDING,
REGISTERED,
BANNED,
ACTIVE
}
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#javax.persistence.Enumerated
private UserType userType;
}
If you really need separated table and #OneToOne relation, you can use #Formula from Hibernate:
#Formula("(select ut.value from user_type ut where ut.ID = USER_TYPE)")
private String userType;
For this really special requirement you could use SecondaryTable annotation.
That is, you don't need UserType entity, but declare attribute userType as String in User entity with column mapping to the secondary table "USER_TYPE".
First of all, I suggest you use ManyToOne relation. and Not CascadeType.ALL if you are not planning update or delete on USER_TYPE table.
If you do not need adding new UserTypes frequently use enum for it. It will just work as you want.
Second solution: As long as fetch = FetchType.EAGER you can add A transient field and return value of UserType in getter.

How can I mark a foreign key constraint using Hibernate annotations?

I am trying to use Hibernate annotation for writing a model class for my database tables.
I have two tables, each having a primary key User and Question.
#Entity
#Table(name="USER")
public class User
{
#Id
#Column(name="user_id")
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name="username")
private String username;
// Getter and setter
}
Question Table.
#Entity
#Table(name="QUESTION")
public class Questions extends BaseEntity{
#Id
#Column(name="question_id")
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#Column(name="question_text")
private String question_text;
// Getter and setter
}
And I have one more table, UserAnswer, which has userId and questionId as foreign keys from the above two tables.
But I am unable to find how I can reference these constraints in the UserAnswer table.
#Entity
#Table(name="UserAnswer ")
public class UserAnswer
{
#Column(name="user_id")
private User user;
//#ManyToMany
#Column(name="question_id")
private Questions questions ;
#Column(name="response")
private String response;
// Getter and setter
}
How can I achieve this?
#Column is not the appropriate annotation. You don't want to store a whole User or Question in a column. You want to create an association between the entities. Start by renaming Questions to Question, since an instance represents a single question, and not several ones. Then create the association:
#Entity
#Table(name = "UserAnswer")
public class UserAnswer {
// this entity needs an ID:
#Id
#Column(name="useranswer_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "question_id")
private Question question;
#Column(name = "response")
private String response;
//getter and setter
}
The Hibernate documentation explains that. Read it. And also read the javadoc of the annotations.
There are many answers and all are correct as well. But unfortunately none of them have a clear explanation.
The following works for a non-primary key mapping as well.
Let's say we have parent table A with column 1
and another table, B, with column 2 which references column 1:
#ManyToOne
#JoinColumn(name = "TableBColumn", referencedColumnName = "TableAColumn")
private TableA session_UserName;
#ManyToOne
#JoinColumn(name = "bok_aut_id", referencedColumnName = "aut_id")
private Author bok_aut_id;
#JoinColumn(name="reference_column_name") annotation can be used above that property or field of class that is being referenced from some other entity.

Categories

Resources