Spring Jpa Entity - EntityManager.getReference - spring

I have a Spring Boot application using Spring JPA, and what I'm trying to do is to save a new entity that has some foreign keys by just providing the IDs of those child entities. So, like:
#Table(name = "PERSON")
public class Person {
#Column(name = "PET_GUID")
public Pet pet;
}
Using this, I'd like to be able to have my PersonRepository that implements CrudRepository save a Person by just providing the guid of the Pet. Using straight up hibernate I can do that using EntityManager.getReference. I know that I can inject an EntityManager into my Entity or Repository and do something that way, but is there an easier way? I tried just doing person.setPet(new Pet(myPetsGuid)), but I get a "foreign key not found" when doing that, so that does not seem to work.

First, you should add #ManyToOne relation to the pet property:
#Entity
#Table(name = "PERSON")
public class Person {
//...
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "pet_guid")
privat Pet pet;
}
It says to Hibernate to use a foreign key to the Pet entity (and its table).
Second, you should use the method getOne of your PersonRepository to get a reference to the Pet entity, for example:
#Service
public class PersonService {
private final PetRepository petRepo;
private final PersonRepository personRepo;
//...
#NonNull
#Transactional
public Person create(#NonNull final PersonDto personDto) {
Person person = new Person();
//...
UUID petId = personDto.getPetId();
Pet pet = petRepo.getOne(perId);
person.setPet(pet);
//...
return person.save(person);
}
}

Related

Issue in persisting nested comments using Spring Hibernate

I am trying to create a simple CRUD application using Spring Boot with User, UserEntity, Post, Comment entities.
-> UserEntity is super class of Comment and Post.
-> Each comment has a ManyToOne relationship to a UserEntity (which can be a Post or another Comment)
UserEntity
   |
   #ManyToOne
   createdBy - refers to user table (id)
   |
--------------------
|        |
|        |
Post    Comment
        |
        #ManytoOne
          UserEntity - refers to PK(entity_id) of user_entity table as comment can be on post or reply to another comment
On trying to save a comment on post from the CommentService class,
//Controller
#PostMapping(path = "api/v1/addComment")
public void addComment(#RequestBody Comment comment){ commentService.addCommentOnPost(comment); }
//Service
public void addCommentOnEntity(Comment comment){ commentRepos.save(comment); }
the foreign key in comment table (parent_entity_id) referring to entity_id in user_entity table is not getting updated. The value is blank.
On the other hand UserEntity has a manytoone relationship with User -- createdBy -- which is updating foriegn key user_id in user_entity table properly
Can someone guide me what could be wrong, I have been trying since yesterday night but no luck. Have checked some other answers but could not get an answer for this case.
User.java
#Entity
#Table(name="[user]")
public class User {
#Id
#SequenceGenerator(name="student_sequence",
sequenceName = "student_sequence",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "student_sequence")
private long id;
private String name;
private String email;
private int age;
private LocalDate DOB;
//Setters and Getters and default constructor
}
UserEntity.java
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class UserEntity {
#Id
#SequenceGenerator(sequenceName = "entity_sequence", name="entity_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "post_sequence")
private long entityId;
private char entityType;
private LocalDate createdOn;
private LocalDate modifiedOn;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User createdBy;
//Setters and Getters and default constructor
}
Post.java
#Entity
public class Post extends UserEntity{
private String postHeading;
private String postBody;
//Setters and Getters and default constructor
}
Comment.java
#Entity
public class Comment extends UserEntity{
private String comment;
#ManyToOne
#JoinColumn(name="parent_entity_id", referencedColumnName = "entityId")
private UserEntity parentEntity;
//Setters and Getters and default constructor
}
and their repositories
#NoRepositoryBean
public interface UserEntityBaseRepos<T extends UserEntity> extends JpaRepository<T, Long>{
Optional<List<T>> findByCreatedBy_Id(Long user_id);
Optional<List<T>> findByEntityId(Long entity_id);
}
#Repository
public interface UserRespository extends JpaRepository<User, Long> {
Optional<User> findUserByEmail(String email);
Optional<User> findUserByName(String name);
}
#Repository
public interface PostRepos extends UserEntityBaseRepos<Post>, JpaRepository<Post, Long> {
}
#Repository
public interface CommentRepos extends UserEntityBaseRepos<Comment>, JpaRepository<Comment, Long> {
}
Json for postComment service
{
"entityType" : "C",
"createdOn" : "2020-02-05",
"createdBy" : {
"id" : 1
},
"comment": "I am the comment",
"parentEntity" : {
"entityId" : 1
}
}
//User with id = 1 and UserEntity(Post) with entityId = 1 available in database.
Here createdBy.id (user id) is getting updated in the user_entity table, but userEntity.entityId is not getting updated in the comment table
You have very complex entity relationships, it seems to me...
Anyway, I found that you added a generator property to the UserEntity entity with a post_sequence value, but I can't find any relationship to the Post entity in your database. This is probably the reason of the breakdown. You have to connect UserEntity to Post as shown on your diagram or change the generator value.
I was able to solve the problem. The issue was in the following piece of code in Comment concrete class
#ManyToOne
#JoinColumn(name="parent_entity_id", referencedColumnName = "entityId")
private UserEntity parentEntity;
and this Json input
"parentEntity" : {
"entityId" : 1
}
It seems the parentEntity in json input was not being parsed. This was solved on placing JsonProperty("parentEntity") above parentEntity in the Json input was being parsed correctly.
However there was another issue. The parentEntity was not being deserialized to UserEntity as UserEntity is an abstract class. I had to use JacksonPolymorphicDeserialization by introducing a new field parentType("P" for post, "C" for comment) along with some Annotations like below to deserialize parentEntity to corresponding concrete class object.
public class Comment extends UserEntity{
private String comment;
#Transient
#JsonProperty("parentType")
private char parentType;
#ManyToOne
#JoinColumn(name="parent_entity_id", referencedColumnName = "entity_id", foreignKey = #ForeignKey(value=ConstraintMode.CONSTRAINT))
#JsonProperty("parentEntity")
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME , property = "parentType", include = JsonTypeInfo.As.EXTERNAL_PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value = Comment.class, name = "C"),
#JsonSubTypes.Type(value = Post.class, name = "P")
})
private UserEntity parentEntity;
reference - Jackson Polymorphic Deserialization via field. I am not really sure how this works. Will try to make sense of it and update the answer.
If anyone knows a better way to deserialize json, do mention it in the comments or as a new answer.

hibernate entity different implementation

I have a project with multiple implementation and an entity class Person.
In every implementation there is a different database, different table and different columns.
In the DAO layer and the business layer the code is the same.
How can I change only the persistence layer to have different implementation of Person entity class based on a profile and keep unchanged the rest of the code?
//I would like to change table and columns based on a profile
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
private String first_name;
private String last_name
//getters,setters
}
//I would like to keep DAO unchanged no matter the profile
public interface PersonDao {
public List<Person> listAll() throws Exception;
}
public class PersonDaoImpl implements PersonDao{
#Override
public List<Person> listAll() throws Exception{
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> criteria = criteriaBuilder.createQuery(Person.class);
Root<Person> root = criteria.from(Person.class);
...the rest of the code
}
}
Have a generic abstract Person and PersonDao which will be extended/implemented by other classes (e.g. MongoPerson, MysqlPersonDao, PersonV2... based on your requirements). But use only Person and PersonDao in your service layer.
Autowire with Spring using qualifiers and configurations

Hibernate Spring repository inheritance add more details to an Entity

I'm using hibernate and spring repository. I have 2 classes Person and PersonDetails which contains more details about the person.
#Entity
#Table(name = "person")
#Inheritance(strategy = InheritanceType.JOINED)
public class Person{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
//name,birthdate ...
}
#Entity
#Table(name = "person_details")
#PrimaryKeyJoinColumn(name="id")
public class PersonDetails extends Person{
// private details accessible only for authorized user
}
When I create a Person I can't associate a PersonDetails it creates automatically a new instance of PersonDetails and add a new line in Person table.
Here's my repositories.
#NoRepositoryBean
public interface PersonBaseRepository<T extends Person> extends JpaRepository<T, Long> {
T findByNameAndFirstname(String name,String firstname);
}
#Transactional
public interface PersonRepository extends PersonBaseRepository<Person> {
}
#Transactional
public interface PersonDetailsRepository extends PersonBaseRepository<PersonDetails > {
}
To solve this I could instanciate my Entities only with PersonDetails but in some cases PersonDetails fields will be empty.
When I call findByNameAndFirstname from PersonDetailsRepository and the person isn't in person_details table but only in person I want to return the person matches in PersonDetails object.
Does anyone have a workable solution ? Thanks for your help.

Spring Jpa: Lazy fetching for ManyToOne

I have Record entity:
#Entity
public class Record implements Serializable {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private User user;
...
and corresponding RecordRepository repository:
public interface RecordRepository extends JpaRepository<Record, Integer> {
List<Record> findByUser(User user);
...
Whenever I call findByUser the resulted records contains users. But I would like to achieve that users will not be fetched from database (record.user == null).
Thanks for any advice!
If the Records are fetching User objects together via this findByUser method, try to rewrite it so it uses just the ID instead:
public interface RecordRepository extends JpaRepository<Record, Integer> {
List<Record> findByUserId(Long id);
}
This should not fetch the User for the Record until you touch it.
Now if you don't want to send the User to the output, you still have to ignore it on that side. E.g. using #JsonIgnore on the Record.getUser() method, or just don't map it in your DTO converter if you are using DTOs (which you should, if you have different representations of the same entity).

Spring Data JPA remove child entities

I have a load repository.
#Transactional
public interface MyLoadRepository extends CrudRepository<ParentEntity, Serializable> {
}
Then is my ParentEntity.
#MappedSuperclass
public class ParentEntity {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
#Column(name = "id", unique = true)
private String uuid;
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
}
Then I have multiple child entities.
#Entity
#Table(name = "EntityA")
public class EntityA extends ParentEntity {
}
#Entity
#Table(name = "EntityB")
public class EntityB extends ParentEntity {
}
Ques : I want to delete these entities separately by my repository.
If I write something like this.
#Autowired
private MyLoadRepository repository;
and then repository.deleteAll()
I get error that repository is not entity (It obiviously not).
Here I want to delete either entityA or entityB data completely based on some condition. How can I do that ?
We should create repository per entity and not on non entity classes.
So, for your case you need 2 repository classes
#Transactional
public interface EntityARepo extends CrudRepository< EntityA, String> {
}
#Transactional
public interface EntityBRepo extends CrudRepository< EntityB, String> {
}
now in service classes you can do
#Autowired
private EntityARepo repoA;
#Autowired
private EntityBRepo repoB;
and then you can call delete method based on your condition
repoA.deleteAll()
or
repoB.deleteAll()
You need to fetch the entity based on a condition. For example, if the EntityA has a primary key uuid, then you must find EntityA by uuid and then delete the EntityA.
EntityA entityA = entityARepo.findOne(uuid);
repository.delete(entityA);
EntityB entityB = entityBRepo.findOne(uuid);
repository.delete(entityB);

Resources