Spring Data JPA inserting instead of Update - spring

Hi I am new to Spring Data JPA and I am wondering even though I pass the Id to the entity, the Spring data jpa is inserting instead of merge. I thought when I implement the Persistable interface and implement the two methods:
public Long getId();
public Boolean isNew();
It will automatically merge instead of persist.
I have an entity class called User like:
#Entity
#Table(name = "T_USER")
public class User implements Serializable, Persistable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "USER_ID")
private Long id;
#Column(name = "CREATION_TIME", nullable = false)
private Date creationTime;
#Column(name = "FIRST_NAME", nullable = false)
private String firstName;
#Column(name = "LAST_NAME", nullable = false)
private String lastName;
#Column(name = "MODIFICATION_TIME", nullable = false)
private Date modificationTime;
And have another class
#Entity
#Table(name = "T_USER_ROLE")
public class UserRole implements Serializable, Persistable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long roleId;
#Column(name = "ROLE_NAME")
private String userRole;
}
I have a custom repository called UserRepostory extending JpaReopistory. I am hitting the save for merge and persist as I see the implementation demonstrate that Spring Data Jpa uses above two methods to either update or insert.
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
I have been trying to figure out but didn't get any clue. Maybe you
guys can help.

I ran into this issue, tried to implement Persistable to no avail, and then looked into the Spring Data JPA source. I don't necessarily see this in your example code, but I have a #Version field in my entity. If there is a #Version field Spring Data will test that value to determine if the entity is new or not. If the #Version field is not a primitive and is null then the entity is considered new.
This threw me for a long time in my tests because I was not setting the version field in my representation but only on the persisted entity. I also don't see this documented in the otherwise helpful Spring Data docs (which is another issue...).
Hope that helps someone!

By default Spring Data JPA inspects the identifier property of the given entity. If the identifier property is null, then the entity will be assumed as new, otherwise as not new. It's Id-Property inspection Reference
If you are using Spring JPA with EntityManager calling .merge() will update your entity and .persist() will insert.
#PersistenceContext
private EntityManager em;
#Override
#Transactional
public User save(User user) {
if (user.getId() == null) {
em.persist(user);
return user;
} else {
return em.merge(user);
}
}
There is no need to implement the Persistable interface.

Related

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
}

Updating object with null field in Spring

I'm working with Spring App, so to work with DB I use Spring Data JPA. Firstly I saved an object. And after some time I need to update this object in the table. But at this moment my object contains one field which is null. But I don't want to update this field with null. So my question is how to prevent updating fields with null? Maybe there is an annotation or some property to solve my problem.My entity:
#Entity
#Table(name = "users")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long id;
#NotNull
#Column(name = "user_name")
#Field
private String username;
#NotNull
#Column(name = "user_identity")
private String identity;
#Column(name="user_image")
private String image;
#Column(name="user_joined")
private String date;
#Column(name="user_origin")
private String origin;
#Column(name="user_sub")
private String sub;
I save and update this entity with implementation of JpaRepository:
#Repository
public interface UserRepository extends JpaRepository<User, Long>
it looks like this:
#Autowired
private UserRepository userRepository;
....
userRepository.save(user);
I've saved my object with not null sub-field. And now I want to update some fields of saved entity, but not sub field, which is null in current object. I wonder if there is any possibility to avoid changing user_sub field to null?
You can add #DynamicUpdate annotation to your User class. This will ignore the fields whose values are null. You can simply do like:
//other annotations
#DynamicUpdate
public class User {
// other codes inside class
}
You can follow a good example from Mkyong's site.
Thanks, guys. I found the solution: #Query will help to update fields that I need

Lazy loading with JPA on #ManyToOne

I have a project with Spring Boot, and I cannot make lazy loading work. I have 2 entities: Question and Answer. A question can have many answers.
What I want is, when I try to get an answer, to get only the answer without the question. And also, if I want both, to have this possibility.
What I did, is I added in application.yml: spring.jpa.open-in-view: true.
The Answer entity is like:
#Entity
#Table(name = "mst_ans_answer", schema = "lquest_sc")
public class Answer implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "lquest_sc.mst_ans_answer_ans_lqs_id_seq")
#SequenceGenerator( name = "lquest_sc.mst_ans_answer_ans_lqs_id_seq", sequenceName = "lquest_sc.mst_ans_answer_ans_lqs_id_seq")
#Column(name = "ans_lqs_id")
private int id;
#Column(name = "qst_lqs_id")
private int questionId;
#Column(name = "ans_text")
private String text;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "qst_lqs_id", insertable=false, updatable=false)
#JsonIgnore
private Question question;
//getters and setters
}
The Question entity is:
#Entity
#Table(name = "mst_qst_question", schema = "lquest_sc")
public class Question implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "lquest_sc.mst_qst_question_qst_lqs_id_seq")
#SequenceGenerator(name = "lquest_sc.mst_qst_question_qst_lqs_id_seq", sequenceName = "lquest_sc.mst_qst_question_qst_lqs_id_seq")
#Column(name = "qst_lqs_id")
private int id;
#Column(name = "qst_title")
private String title;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "question")
#OrderBy("order asc")
private Set<Answer> answers = new HashSet<Answer>();
//getters and setters here
The call in the controller is:
#RequestMapping(value = "/questionId/{id}", method = RequestMethod.GET)
public List<Answer> listAll( #PathVariable("id") int id ){
List<Answer> answers = answerRepository.findByEnabledAndQuestionIdOrderByOrderAsc(1,id);
return answers;
}
and the repository is
public interface AnswerRepository extends JpaRepository<Answer, Long> {
List<Answer> findByEnabledAndQuestionIdOrderByOrderAsc(int enabled,int questionId);
}
The problem is that in the controller, when I try to evaluate
answers.get(0).getQuestion(), I receive the entity of Question, with the properties filled with null values and the error Method threw 'org.hibernate.LazyInitializationException' exception. Cannot evaluate Question_$$_jvst5b6_1.toString(). What am I doing wrong?
I don't know why spring.jpa.open-in-view = true is not working in your case. Maybe the OpenEntityManagerInViewInterceptordoes not get triggered or the thread has been left, when you are evaluating the Question. Or you have an older version which just does not support it.
Lazy loading works only inside of a transaction. A solution could be -
as #Pradeep already gave you the hint - to use #Tranactional inside a business logic class.
Even if you put the #Transactional inside your repository it will not work, because you have to put the annotation on top of the method where you are trying to evaluate answers.get(0).getQuestion().
Furthermore I advise you not to call your repository from the controller directly, but to use a service layer, where you put your business logic.
Example implementation
This is only a example implementation to show you how to structure your application and what the important keywords are. Also note, that you can either use #Inject or #Autowired. When you have implemented your logic, just inject the service into your controller and use it there.
AnswerService.java
public interface AnswerService {
List<Answer> findByEnabledAndQuestionIdOrderByOrderAsc(int enabled, Long id);
}
AnswerServiceImpl.java
#Service
public class AnswerServiceImpl implements AnswerService {
private AnswerRepository answerRepository;
#Inject
public AnswerServiceImpl(AnswerRepository answerRepository) {
this.answerRepository = answerRepository;
}
#Transactional
#Override
public List<Answer> findByEnabledAndQuestionIdOrderByOrderAsc(int enabled, Long id) {
List<Answer> answerList = findByEnabledAndQuestionIdOrderByOrderAsc(int enabled,int questionId);
// do your lazy loading here
// because you are still in the same transactional context
// return the list
return answerList;
}
}

Fetch specific property in hibernate One-to-many relationship

I have two pojo classes with one-to-many relationship in hibernate
CustomerAccountEnduserOrderDetails.class
#Entity #Table(name="customer_account_enduser_order_details")
public class CustomerAccountEnduserOrderDetails implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id")
private Long id;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "product_id", insertable = false, updatable = false)
private CustomerCmsProduct customerCmsProduct;
}
Second is CustomerCmsProduct.class
#Entity
#Table(name="customer_cms_product")
#JsonIgnoreProperties(ignoreUnknown = true)
public class CustomerCmsProduct {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
private Long id;
#Column(name="name")
private String name;
#Column(name="offer_price")
private String offerPrice;
#Column(name="original_price")
private String originalPrice;
#Column(name="discount")
private String discount;
}
Here if I fetch the object of CustomerAccountEnduserOrderDetails class,then i will get the CustomerCmsProduct class also , my problem is that here i want the specific column of CustomerCmsProduct table (not all by default i am getting all) like only id and originalPrice.
How i can do like that projection here?
In the service layer or at a webservice layer( if this is a web project) Create two different classes other than #Entity as DTO(Data Transfer Objects) which helps is data transfer from one layer to the other.
public class CustomerAccountEnduserOrderDetailsPojo {
private List<CustomerCmsProductPojo> productPojoList = new ArrayList<> ();
// getter and setter
}
public class CustomerCmsProductPojo {}
Follow the below steps
Retrieve the #Entity class data by executing the query from service layer.
Iterate over all the fields and copy only required fields to pojo layer
Expose the data to other layers using service method.
This way, we can avoid changing the custom hibernate behavior as it is linked with many parameters like cache, one to many queries that are fired per iteration.
And also, do any customization that you want in this layer. Hope this is multi layered project where you have different layers which servers different purpose.

Auto generating create date and last modify date using JPA data CrudRepositories

I'm trying to migrate my app to generate the schema using the entities, instead of having a pre existing schema. Up until now, the create and lastModify dates where updated by the DB (MySql), but now since i want to generate the schema from JPA, i must do this programatically.
Before, i always used #PrePersit and #PreUpdate to do this without any problems but now it doesn't work and i think it's because i use spring data's CrudRepository Interfaces for my DAOs, so i don't know what's going on with the entity manager here...
I did some research and i found a few thing about spring auditing, but i couldn't get it to work. at the moment i'm trying the #Created and #LastModified annotations, but they don't work. this is what i have now: my abstractentity:
#MappedSuperclass
#Data
#ToString
#EqualsAndHashCode
public abstract class AbstractEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#SequenceGenerator(name = "seq")
#Column(name = "ID")
private Long id = 0L;
#CreatedDate
// #Generated(GenerationTime.INSERT)
#Column(name = "CREATED", insertable = true, updatable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date created;
#LastModifiedDate
#Version
// #Generated(GenerationTime.ALWAYS)
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "LAST_MODIFIED", insertable = false, updatable = true)
private Date lastModified;
/**
* copies the auto generated fields id, created and last modified form the given entity/DTO to this entity/DTO
*
* #param copyEntity the entity to copy from.
*/
public void copy(AbstractEntity copyEntity) {
this.setId(copyEntity.getId());
this.setCreated(copyEntity.getCreated());
this.setLastModified(copyEntity.getLastModified());
}
}
my configuration:
#Configuration
#ActiveProfiles("development")
#EnableTransactionManagement
#EnableJpaRepositories
#EnableJpaAuditing(setDates = false, auditorAwareRef = "auditorAware")
public class RepositoryTestContext {
#Bean
public AuditorAware<String> auditorAware() {
return new AuditorAware<String>() {
#Override
public String getCurrentAuditor() {
return "dummy";
}
};
}
}
Basically my tests show that the create date and last modify date are not being updated. any ideas???
I think you are missing the #EntityListeners annotation on your AbstractEntity :
#EntityListeners({AuditingEntityListener.class})
#MappedSuperclass
#Data
#ToString
#EqualsAndHashCode
public abstract class AbstractEntity implements Serializable {
I think you have set it of in this line:
#EnableJpaAuditing(setDates = false, auditorAwareRef = "auditorAware")
This is from Spring docs, Frequently asked questions:
Question: I want to use Spring Data JPA auditing capabilities but have my database already set up to set modification and creation date on entities. How to prevent Spring Data from setting the date programmatically.
Answer:Just use the set-dates attribute of the auditing namespace element to false.
I think those annotations are Hibernate 5.2+++
And I think maybe you're using Hibernate 5.0.x or something lower.
I can't tell for sure though without seeing your POM. Just make sure your current version supports this annotation from hibernate.annotations.

Resources