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

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

Related

Removing entities from another entity but not from database

I have two entities: Player and Team:
Player:
#Data
#Entity
public class Player {
#Column(nullable = false)
private String username;
#OneToOne
#JoinColumn(name = "team")
#JsonIgnore
private Team team;
}
Team:
#Entity
#Data
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToOne(fetch = FetchType.EAGER)
private User captain;
#OneToMany(fetch = FetchType.EAGER)
private List<User> players;
}
TeamRepository:
#Repository
public interface TeamRepository extends JpaRepository<Team, Long> {
}
I want to delete the whole Team from database using delete() method from JPA. Unfortunately when I do so, all Players are also deleted from my database. I only want to detach them from the Team.
Adding cascade = CascadeType.ALL/DELETE to Team and calling delete() method results in User entities deletion from database, and that's what I don't want to happen.
Adding cascade = CascadeType.DETACH to Team and calling delete() throws me a
java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`32308008_localdb`.`users`, CONSTRAINT `FKhn2tnbh9fqjqeuv8ehw5ple7a` FOREIGN KEY (`team_id`) REFERENCES `team` (`id`))
exception and I have no idea why.
If someone have an idea how to delete Team entity and detach User from context but not from database I would be VERY thankful!

Get id's of a ManyToMany mapping table

I'm writing an API using Spring Boot and Hibernate where my persisted entity objects are also used as DTOs sent to and from the client. This is a simplified version of a typical entity I use:
#Entity
#Table(name = "STUDENT")
public class Student {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#ElementCollection
#CollectionTable(name = "GROUP_STUDENT",
joinColumns = #JoinColumn(name = "GROUP_ID"))
#Column(name="STUDENT_ID")
private Set<Long> groupIds;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name="GROUP_STUDENT",
joinColumns = #JoinColumn(name="GROUP_ID"),
inverseJoinColumns = #JoinColumn(name="STUDENT_ID")
)
private Set<Group> groups = new HashSet<>();
// getters and setters
}
and this is the associated class:
#Entity
#Table(name = "GROUP")
public class Group {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "groups")
private Set<Student> students = new HashSet<>();
// getters and setters
}
As you can see, there is a #ManyToMany association between Student and Group.
Since I send objects like these to the client, I choose to send only the id's of the associations and not the associations themselves. I've solved this using this answer and it works as expected.
The problem is this. When hibernate tries to persist a Student object, it inserts the groups as expected, but it also tries to insert the groupIds into the mapping table GROUP_STUDENT. This will of course fail because of the unique constraint of the mapping table composite id. And it isn't possible to mark the groupIds as insertable = false since it is an #ElementCollection. And I don't think I can use #Formula since I require a Set and not a reduced value.
This can of course be solved by always emptying either the groups of the groupIds before saving or persisting such an entity, but this is extremely risky and easy to forget.
So what I want is basically a read only groupIds in the Student class that loads the data from the GROUP_STUDENT mapping table. Is this possible? I'm grateful for any suggestions and glad to ellaborate on the question if it seems unclear.
I've managed to solve this by making the id-collection #Transient and populating it using #PostLoad:
#Entity
#Table(name = "STUDENT")
public class Student {
#PostLoad
private void postLoad() {
groupIds = groups.stream().map(Group::getId).collect(Collectors.toSet());
}
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#Transient
private Set<Long> groupIds;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name="GROUP_STUDENT",
joinColumns = #JoinColumn(name="GROUP_ID"),
inverseJoinColumns = #JoinColumn(name="STUDENT_ID")
)
private Set<Group> groups = new HashSet<>();
// getters and setters
}

Strange behaviour of spring data jpa

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.

Dropwizard Hibernate 1.0.0

I'm having an issue with a bi-directional mapping between a one-to-many relationship whereby the many is not getting it's foreign key updated - it remains null when performing an update/create. There are two entities, the first (many) is this:
#Entity
#Table(name = "availability")
public class Availability implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PERSON_ID", nullable = false)
private Person person;
The second is the one:
#Entity
#Table(name = "PERSON")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "discriminator", discriminatorType = DiscriminatorType.STRING)
#DiscriminatorValue(value = "P")
public abstract class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "person")
private Collection<Availability> availabilities;
There are a couple of things that might be causing this, but I'm uncertain of what the real reason is. The first is that Person is abstract. However the inheritence type I'm using should not be causing this issue.
The second is the way in which I'm trying to persist the entity in a single transaction. I'm using dropwizard's #UnitOfWork. My DAO is doing the following:
public Staff update(Staff staff) {
return persist(staff);
}
Staff at this point does not have an id. Availability is set on Person (also without an id)
Under the hood, this is simply calling the following:
protected E persist(E entity) throws HibernateException {
currentSession().saveOrUpdate(requireNonNull(entity));
return entity;
}
Should i be using the session manager to open a new transaction, persist person, get the id back, set that on availability and then perform an update with person with the modified availability? It seems to me that would just be wrong.
The problem is that it wasn't really a bi-directional relationship, and Person was never being set on availability - hence why the foreign key was null.
I modified it to unidirectional.
On Person.class
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "person_availability")
private List<Availability> availabilities;
and then on Availability.class I removed the bidirectional reference i.e. removed the person value reference.
The other change required was setting the transactional strategy with the cascade type.
Field annotation strategy is not the issue here.

How to read attribute from relationed object using JsonManagedReference

I've got two objects: UserEntity and ApartmentEntity that are relationed OneToMany (one user can have many apartments).
I had problem with serializing it into JSON with infinite recursion ( I solved it using #JsonManagedReference and #JsonBackReference: Infinite Recursion with Jackson JSON and Hibernate JPA issue ), but now i can't read user_id from Apartments table (i need to know which user own current apartment).
ApartmentEntity:
#Entity
#Table(name = "users")
#Scope("session")
public class UserEntity {
#Id
#Column(name = "user_id")
#GeneratedValue
public int user_id;
//other fields ...
#JsonManagedReference
#OneToMany(mappedBy="user", cascade = { CascadeType.MERGE }, fetch=FetchType.EAGER)
private List<ApartmentEntity> apartments;
//getters setters ...
}
ApartmentEntity
#Entity
#Table(name = "apartments")
public class ApartmentEntity {
#Id
#Column(name = "id")
#GeneratedValue
private int id;
// ...
#JsonBackReference
#ManyToOne
#JoinColumn(name="user_id")
private UserEntity user;
//getters, setters ...
}
And now returned JSON don't have user_id inside Apartment attributes.
How to solve this problem? How to read some attribute anyway, using that #Json annotations. I'm stuck here.
JsonManagedReference, JsonBackReference work one way. So UserEntity JSON contains ApartmentEntities, but ApartmentEntity won't contain UserEntity.
Jackson provides a better mechanism to avoid circular references:
http://wiki.fasterxml.com/JacksonFeatureObjectIdentity
Since you already have an id field using JPA, I would recommend using PropertyGenerator on the entity classes:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "user_id", scope = UserEntity.class)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = ApartmentEntity.class)

Categories

Resources