Issue in persisting nested comments using Spring Hibernate - spring

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.

Related

How can i get all my saved posts in spring boot?

I have to entities like User and Food. User can save food posts. I am trying to getting all saved posts of user but how can i do this? I am writing hibernate query in food repository but i can't access saved food posts.
Here is my code:
#Data
#Entity
public class User extends BaseEntity {
#Column(unique = true, nullable = false)
private String username;
#JsonIgnore
#OneToMany
private List<Food> savedRecipes;
}
Food class:
#Data
#Entity
#Where(clause = "deleted = false")
public class Food extends BaseEntity {
private String foodName;
private String recipe;
#OneToMany
private List<Category> categoryList;
#ManyToOne(fetch = FetchType.EAGER)
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
}
Repository Code:
#Repository
public interface FoodRepository extends JpaRepository<Food,Long> {
List<Food> findAllByFoodNameContaining(String searchedValue);
List<Food> findAllByCategoryListInAndDeletedFalse(List<Category> categoryList);
List<Food> findAllByUserId(Long id);
List<Food> findAllByUserSavedRecipes(Long id);
}
Try this way.
List <Food> findAllByUser (User user);
You can easily get All User's saved food by call below methods
1st: List<Food> result = User.getSavedRecipes()
2nd: List<Food> result = FoodRepository.findAllByUserId(Long id)

JPARepository CPRQ modified does not save full object

I have modified the design of CPRQ a bit to help my database pattern
I have an Employee table and a Department table. Both have common properties
#Column(name="tenantIDPKFK")
private Integer tenantIdpkfk;
#Column(name="status")
private Integer status;
So I created a base class ABaseEntity like below
public class ABaseEntity {
public ABaseEntity() {
}
public ABaseEntity(int tenantIdpkfk, int status) {
this.tenantIdpkfk = tenantIdpkfk ;
this.status = status ;
}
#Column(name="tenantIDPKFK")
private Integer tenantIdpkfk;
#Column(name="status")
private Integer status;
I have extended EmployeeEntity with ABaseEntity
#Entity
#Table(name = "employee")
public class EmployeeEntity extends ABaseEntity{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "first_name")
#NotEmpty(message = "Please provide a name")
#NotBlank
private String firstName;
My CommandHandler runs the following code
EmployeeEntity savedEmployeeEntity = this.employeeRepository.saveAndFlush(employee);
this.mediator.emit(new EmployeeCreatedEvent(savedEmployeeEntity.getId()));
Database saved the object, but only id, firstname. Does not save tenant and status columns.
I know I am missing something silly. Please help.
EDIT
Adding #MappedSuperclass to the ABaseEntity class fixed the issue.
#MappedSuperclass
public class ABaseEntity {...}
Database saved the object, but only id, firstname. Does not save
tenant and status columns.
By default JPA doesn't consider the parent class in the orm (object-relational mapping) of the current class.
You have to specify on the parent class #Inheritance with the strategy to use or use the default one.
For example :
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class ABaseEntity {...}
More info here.

How can I add a tenant condition to Spring Data JPA Default and Dervied Queries

I have a Springboot Application with Repositories having Spring Data JPA Queries like findOne, findAll and also derived ones like findByID or findByName etc.
What I want to achieve is multitenancy. All entities have an "account_id" column which holds the tenant.
How do I add a filter like "account_id" to all the queries metioned above without using derived queries that contains those name slike findIdAndAccountid (which would be findone)
#Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
Category findByName(String name);
}
Here's the corresponding entity
#Entity
#Table(name = "unit")
#Data
public class Unit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
I know most people use schemas as tenant separation but that's impossible for me. Is there a way (I didn't find one) to add such a tenant filter condition on those queries without writing NamedQueries or using DerivedQueries. An elegeant solution like annotate the repository or entity or maybe the queries that all queries should add the additional filter "account_id"?
You can add Where clause on your Entity classes (Didnt had time to test )
#Entity
#Table(name = "unit")
#Data
#Where(clause = "account_id= :account_id")
public class Unit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
Update and Solution
1. Create a Filter & FilterDef on the entity like so
#FilterDef(name="accountFilter", parameters=#ParamDef( name="accountId", type="long" ) )
#Filters( {
#Filter(name="accountFilter", condition=":accountId = account_id")
} )
public class Category {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
enable filtering in the controller by autowiring entitymanager, writing a method to enable the filter and activate the filter in #ModelAttribute for each request
#RestController
#RequestMapping(path = "/categories",produces = MediaType.APPLICATION_JSON_VALUE )
public class CategoryController {
private final CategoryRepository repository;
#Autowired
private EntityManager entityManager;
CategoryController(CategoryRepository repository) {
this.repository = repository;
}
private void activateFilter() {
Session session = entityManager.unwrap(Session.class);
Filter filter = session.enableFilter("accountFilter");
filter.setParameter("accountId", Long.valueOf(TenantContext.getCurrentTenant()));
}
#ModelAttribute
public void initFilter() {
activateFilter();
}
... your rest methods here
}

i'm getting null value in a child table as a foreign key of parent table using spring data rest or spring data jpa accosiation

enter image description here In this image first address for empId 1 and last two records are empid 2 (empid 2 haveing to address)
file:///home/user/Pictures/fk.png
#Entity
#Table(name = "Employee")
public class Employee {
#Id
#GeneratedValue
private Integer id;
private String name;
private Integer sal;
#OneToMany(cascade = CascadeType.ALL,mappedBy="employee")
private List<Address> addresses;
//getter setter
Child entity
#Entity(name="Address")
public class Address {
#Id
#GeneratedValue
private Integer aid;
private String city;
private String state;
#ManyToOne
#JoinColumn(name="id")
private Employee employee;
//getter setter
Repository
#Repository
#RepositoryRestResource(path="employee")
public interface EmployeeRepo extends JpaRepository<Employee,Integer> {
}
Input from RestClient
{
"name":"rdhe",
"sal":"20000",
"addresses":[{
"city":"hyd",
"state":"ts"
}]
}
if i use spring data jpa then code will be
// jpa Repository
public interface EmployeeRepo extends JpaRepository<Employee,Integer> {
}
// EmployeeServer class
#Service
public class EmployeeService {
#Autowired
EmployeeRepo employeeRepo;
public void saveEmployee(Employee employee){
employeeRepo.save(employee);
}
}
// controller
#RestController
public class EmployeeController {
#Autowired
EmployeeService employeeService;
#PostMapping(path="/save")
public void saveEmp(#RequestBody Employee employee){
employeeService.saveEmployee(employee);
}
}
if i'll use spring-data-rest at that time no need to create employeeService and controller class
I was getting the same problem until JsonManagedReference came to my rescue.
Try changing your entities to include them like this:
In the Employee Entity:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy ="employee")
#JsonManagedReference
private List<Address> addresses;
In the Address Entity:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id", nullable = false, updatable = false, insertable =true)
#JsonBackReference
private Employee employee;
I was not able to find why it works this way, so please let me know if you come to know :)
It is probably due to the fact that your mentioning #JoinColumn(name="id"). The name attribute in #JoinColumn defines the name of the foreign key field in the child table. Since you are specifying foreign key column as id on hibernate, it could be the issue. Please update it to the same name(ie fk_empid) as specified in database, it should work...

Spring Data Rest #EmbeddedId cannot be constructed from Post Request

I have a JPA entity Person and an entity Team. Both are joined by an entity PersonToTeam. This joining entity holds a many-to-one relation to Person and one to Team. It has a multi-column key consisting of the ids of the Person and the Team, which is represented by an #EmbeddedId. To convert the embedded id back and forth to the request id I have a converter. All this follows the suggestion on Spring Data REST #Idclass not recognized
The code looks like this:
#Entity
public class PersonToTeam {
#EmbeddedId
#Getter
#Setter
private PersonToTeamId id = new PersonToTeamId();
#ManyToOne
#Getter
#Setter
#JoinColumn(name = "person_id", insertable=false, updatable=false)
private Person person;
#ManyToOne
#Getter
#Setter
#JoinColumn(name = "team_id", insertable=false, updatable=false)
private Team team;
#Getter
#Setter
#Enumerated(EnumType.STRING)
private RoleInTeam role;
public enum RoleInTeam {
ADMIN, MEMBER
}
}
#EqualsAndHashCode
#Embeddable
public class PersonToTeamId implements Serializable {
private static final long serialVersionUID = -8450195271351341722L;
#Getter
#Setter
#Column(name = "person_id")
private String personId;
#Getter
#Setter
#Column(name = "team_id")
private String teamId;
}
#Component
public class PersonToTeamIdConverter implements BackendIdConverter {
#Override
public boolean supports(Class<?> delimiter) {
return delimiter.equals(PersonToTeam.class);
}
#Override
public Serializable fromRequestId(String id, Class<?> entityType) {
if (id != null) {
PersonToTeamId ptid = new PersonToTeamId();
String[] idParts = id.split("-");
ptid.setPersonId(idParts[0]);
ptid.setTeamId(idParts[1]);
return ptid;
}
return BackendIdConverter.DefaultIdConverter.INSTANCE.fromRequestId(id, entityType);
}
#Override
public String toRequestId(Serializable id, Class<?> entityType) {
if (id instanceof PersonToTeamId) {
PersonToTeamId ptid = (PersonToTeamId) id;
return String.format("%s-%s", ptid.getPersonId(), ptid.getTeamId());
}
return BackendIdConverter.DefaultIdConverter.INSTANCE.toRequestId(id, entityType);
}
}
The problem with this converter is, that the fromRequestId method gets a null as id parameter, when a post request tries to create a new personToTeam association. But there is no other information about the payload of the post. So how should an id with foreign keys to the person and the team be created then? And as a more general question: What is the right approach for dealing many-to-many associations in spring data rest?
After running into the same issue I found a solution. Your code should be fine, except I return new PersonToTeamId() instead of the DefaultIdConverter if id is null in fromRequestId().
Assuming you are using JSON in your post request you have to wrap personId and teamId in an id object:
{
"id": {
"personId": "foo",
"teamId": "bar"
},
...
}
And in cases where a part of the #EmbeddedId is not a simple data type but a foreign key:
{
"id": {
"stringId": "foo",
"foreignKeyId": "http://localhost:8080/path/to/other/resource/1"
},
...
}

Resources