Spring Data JPA does not fire update query on dirty context - java

I have tried a lot to solve this problem. Read spring data jpa reference documentation with hibernate documentation as well but no luck in this case.
Found spring data jpa examples at github way too short and documentation was not very helpful for novice people like me.
I have following scenario:
My project is in spring boot with spring-data-jpa in repository layer.
Investment can be FixedDepositInvestment, RecurringDepositInvestment or any other subtype. For this, I have used inheritance in hibernate.
Note: getters and setters omitted.
Question might be lengthy
BaseEntity class
#MappedSuperclass
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
}
Investment class
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Investment extends BaseEntity{
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
#Temporal(TemporalType.DATE)
#DateTimeFormat(pattern = "yyyy-MM-dd")
#Column(name = "issue_date", nullable = true)
private Date dateOfIssue;
#CreationTimestamp
#DateTimeFormat(pattern = "yyyy-MM-dd")
#Column(name = "dateCreated")
private Date dateCreated;
#UpdateTimestamp
#DateTimeFormat(pattern = "yyyy-MM-dd")
#Column(name = "lastUpdated")
private Date lastUpdated;
}
CertificateInvestment class
#MappedSuperclass
public abstract class CertificateInvestment extends Investment {
public CertificateInvestment() {
}
public CertificateInvestment(User user, int amountInvested) {
super(user);
this.amountInvested = amountInvested;
}
#Column(name = "amount_invested", nullable = false)
private int amountInvested;
#Column(name = "account_no", nullable = true) // can be folio no or any other name
private String accountNo;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "investment_id", nullable = true)
List<Nominee> nominees;
}
BankCertificateInvestment class
#MappedSuperclass
public abstract class BankCertificateInvestment extends CertificateInvestment{
public BankCertificateInvestment() {
super();
}
public BankCertificateInvestment(User user, int amountInvested, Bank bank) {
super(user, amountInvested);
this.bank = bank;
}
#OneToOne
#JoinColumn(name = "bank_id", nullable = false)
private Bank bank;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "investment_id", nullable = true) //???
private Set<JointHolder> accountJointHolders; // make sure same jh is not added again
#Temporal(TemporalType.DATE)
#DateTimeFormat(pattern="yyyy-MM-dd")
#Column(name = "maturity_date", nullable = true)
private Date dateOfMaturity;
#Column(name = "interest_rate", nullable = true)
private float interestRate;
#Enumerated(EnumType.STRING)
#Column(nullable = true)
private InterestPayable interestPayable;
#Column(nullable = true)
private int durationInMonths;
}
FixedDepositInvestment class
#Entity
#PrimaryKeyJoinColumn(name = "id")
#Table(name = "FixedDepositInvestment")
public class FixedDepositInvestment extends BankCertificateInvestment {
public FixedDepositInvestment() {
super();
}
public FixedDepositInvestment(User user, int amountInvested, Bank bank) {
super(user,amountInvested, bank);
}
#Column(nullable = true)
private String creditToAccountNumber;
}
For this I have created following repository interfaces:
InvestmentRepository interface:
#Transactional(readOnly = true)
public interface InvestmentRepository extends CrudRepository<Investment, Integer> {
}
FixedDepositInvestmentRepository interface :
#Transactional
public interface FixedDepositInvestmentRepository extends CrudRepository<FixedDepositInvestment, Integer> {
}
UserRepository interface :
#Transactional(readOnly = true)
public interface UserRepository extends CrudRepository<User, Integer> {
}
BankRepository Interface :
public interface BankRepository extends CrudRepository<Bank,Integer>{
}
Finally, I have executed following code.
Note: I have directly written code in the controller #RequestMapping method for testing purposes only.
#Controller
public class AddInvestmentController {
#Autowired
FixedDepositInvestmentRepository fixedDepositInvestmentRepo;
#Autowired
UserRepository userRepository;
#Autowired
BankRepository bankRepository;
#Autowired
EntityManager em;
#RequestMapping(value = "/addme", method = RequestMethod.GET)
public String addInvestment() {
User user = new User("Oliver","Gierke","9999999999",new Date(),"123456");
Bank bank = new Bank("tjsb","123456");
userRepository.save(user);
bankRepository.save(bank);
FixedDepositInvestment fd = new FixedDepositInvestment(user, 1000, bank);
fixedDepositInvestmentRepo.save(fd);
fd.setAccountNo("55555");
fd.setAmountInvested(12345);
fd.setAmountInvested(352);
System.out.println("date created : -------------" + fd.getDateCreated() + " " + fd.getLastUpdated());
// System.out.println(fixedDepositInvestmentRepo.findAll());
return "add";
}
}
Queries that get fired are:
Hibernate:
insert
into
User
(address, dob, firstName, lastName, mobile, user_password)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
insert
into
Bank
(address, name, ifsc_code)
values
(?, ?, ?)
Hibernate:
insert
into
Investment
(dateCreated, issue_date, lastUpdated, user_id)
values
(?, ?, ?, ?)
Hibernate:
insert
into
FixedDepositInvestment
(account_no, amount_invested, amount_on_maturity, bank_id, maturity_date, durationInMonths, interestPayable, interest_rate, creditToAccountNumber, id)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
The annoying part is lines
fd.setAccountNo("55555");
fd.setAmountInvested(12345);
fd.setAmountInvested(352);
do NOT fire UPDATE query at all.So, my lastUpdated date is same as dateCreated.
However, If I call
System.out.println(fixedDepositInvestmentRepo.findAll());
It fires an UPDATE then select
Same thing happens in the test cases also.
With same code, if I add
#Autowired
TestEntityManager em;
and
em.flush()
// at method ending, the update is called.
This behaviour is annoying, which raises many questions for me:
Why update is not being called? Spring data jpa does not check for dirty context, do we have to fire a repository.save(entity) again to update changes?
In spring jpa documentation,it has been mentioned, when you configure a transaction as readOnly which causes Hibernate to skip dirty checks (a noticeable improvement on large object trees). .
Now I am confused whether I should use #Transactional with readOnly=true or not.
Do I need to define #NoRepositoryBean for all #MappedsuperClass too? I have seen in some posts, people create these, I do not know if that is needed, in what scenario should I create norepositorybean for such classes?
Have I called queries(methods) in proper format?
Is this problem of hibernate or spring data jpa?
When do we need TestEntityManager? Is it required when we use spring data jpa?

Related

Spring Data JPA save() return entity

I am using spring boot data jpa as below
#Entity
#Table(name = "invoice")
#Getter
#Setter
#ToString
public class Invoice {
#Id
#Column(name = "inv_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private BigInteger invId;
#Column(name = "external_id")
private String externalInvoiceId;
#Column(name = "amount")
private double amount;
#JsonIgnore
#JsonIgnoreProperties
#Column(name = "status")
private int status;
#JsonProperty("status")
#Transient
private String invoiceStatus;
public String getInvoiceStatus() {
switch (this.status){
case 1:
return "INITIATED";
case 2:
return "CANCELLED";
case 3:
return "SUCCESS";
case 4:
return "FAILURE";
default:
return "IN PROGRESS";
}
}
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_at")
private Date createdAt;
#UpdateTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "updated_at")
private Date updatedAt;
#PostPersist
public void updateExternalID() {
this.externalInvoiceId="G".concat(String.valueOf(this.invId.multiply(BigInteger.valueOf(1000))))
.concat(String.valueOf(Instant.now().getEpochSecond()));
}
}
Am accesing this entiry via repository as below
public interface InvoicesRepository extends JpaRepository<Invoice, BigInteger> {
}
At my #Service am performing the below operation
#Autowired
private InvoicesRepository myInvoicesRepository;
Invoice transactionInvoice = new Invoice();
transactionInvoice.setAmount(200.0);
transactionInvoice.setStatus(1);
Invoice savedInvoice = myInvoicesRepository.save(transactionInvoice);
Am using savedInvoice and trying to update the status. Either it is not updating the status properly nor I could not find the record in database too.
There are no rollback present
Below are the logs I could see insert statements are present
[XNIO-1 task-1] DEBUG org.hibernate.SQL.logStatement -
/* insert com.min.app.model.Invoice
*/ insert
into
invoice
(amount, created_at, external_inv_id, status, updated_at)
values
(?, ?, ?, ?, ?)
Hibernate:
/* insert com.min.app.model.Invoice
*/ insert
into
invoice
(amount, created_at, external_inv_id, status, updated_at)
values
(?, ?, ?, ?, ?)
After the status updates I tried printing the savedInvoice could see the below in logs
Invoice(invId=58, externalInvoiceId=G580001575271905, amount=185.0 status=4, invoiceStatus=FAILURE, createdAt=Mon Dec 02 13:01:45 IST 2019, updatedAt=Mon Dec 02 13:01:45 IST 2019)
The above record I could not see in the table.
What am I doing wrong?
you need to perform transactions in a function call as follows and put your #Autowired repository in Global level ass follows.
class whatever{
#Autowired
private InvoicesRepository myInvoicesRepository;
//call this function
void doSomething(){
Invoice transactionInvoice = new Invoice();
transactionInvoice.setAmount(200.0);
transactionInvoice.setStatus(1);
Invoice savedInvoice = myInvoicesRepository.save(transactionInvoice);
}
}

how to find the data by an attribute (name_techno) in jpql

I am developing an application that allows managing candidates in a company, for that I use spring-boot, in order to select the employees who master such a technology (Techno) I used a request JPQL.
So, How can I find a candidate by techno?
In my project I used this code:
1 - the class candidat.java
#Entity
public class Candidat {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "candidat_id")
private int id;
private String nom;
private String prenom;
private String ville;
private int numTel;
private String mail;
private String pseudo;
private String roleCible;
private String typeContrat;
private String villeRecherchee;
#OneToMany(mappedBy="candidat")
private List<Techno> techno;
#Temporal(TemporalType.DATE)
private Date date;
#OneToMany
private List<SecteurActivites> secteurActivites;
public Candidat() {
// TODO Auto-generated constructor stub
}
2- the class Techno.java
#Entity
public class Techno {
#Id
#GeneratedValue
#Column(name = "techno_id")
private int id ;
private String nomTechno;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "candidat_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private Candidat candidat;
public Techno() {
// TODO Auto-generated constructor stub
}
/**
* #param nomTechno
* #param candidat
*/
public Techno(String nomTechno, Candidat candidat) {
super();
this.nomTechno = nomTechno;
this.candidat = candidat;
}
3- My CandidatController
#GetMapping(value = "/GetAllCandidats/{nomTechno}")
public List<Candidat> afficherCandidat(#PathVariable ("nomTechno") String nomTechno){
return candidatdao.findByTechno(nomTechno);
}
4- the repository:
#Repository
public interface CandidatDao extends JpaRepository <Candidat, String>{
List<Candidat> findByDate(Date date);
#Query("SELECT DISTINCT e FROM Candidat e INNER JOIN e.Techno t")
List<Candidat> findByTechno(String nomTechno);
}
5- app.properties
server.port= 9090
spring.jpa.show-sql = true
spring.datasource.url= jdbc:mysql://localhost:3306/database
spring.datasource.username=??
spring.datasource.password=??
spring.jpa.hibernate.ddl-auto=update
The result in console is:
"Validation failed for query for method public abstract java.util.List com.avatar.dao.CandidatDao.findByTechno(java.lang.String)!"
You can declare the following method into your JpaRepository (also remove the #Query, it is not needed).
List<Candidat> findDistinctByTechnoNomTechno(String nomTechno);
Also in Techno.java you should add the #Column annotation and map it with the DB schema.
I am not sure if you have pasted incomplete code of your entities on purpose. If not your entities are not correct. You should create setters/getters as the following
private String nomTechno;
#Column(name = "NOM_TECHNO")
public String getNomTechno() {
return nomTechno;
}
public void setNomTechno(String nomTechno){
this.nomTechno = nomTechno;
}
Do the above for all variables in your entities.
You do not need to add explicit #Query for this, Spring data can formulate a query if you have right method names
Instead of
#Query("SELECT DISTINCT e FROM Candidat e INNER JOIN e.Techno t")
List<Candidat> findByTechno(String nomTechno);
Try this
List<Candidat> findDistinctByTechno_NomTechno(String nomTechno);

How to depict joins with #Query annotation in Spring JPA Repository method

I am using Spring-Boot with JPA and a MySQL backend. Now I got quite confused about the repositories Spring-Boot provides. I know these are quite powerful (and seem to be quite useful since they can shorten your code a lot). Still, I do not understand how to represent Joins within them, since the result-set should be a combination of specified attributes in the select of a few Entities.
Now let's assume we have three tables Book, Author, AuthorOfBook, where the last one is simply connecting Book and Author by a combined Primary key. I guess we had the following Java-Classes:
Entity Book:
#Entity
#Table(name="BOOK")
public class Book {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "TITLE")
private String title;
}
Entity Author
#Entity
#Table(name="AUTHOR")
public class Author {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "LASTNAME")
private String lastname;
#Column(name = "FIRSTNAME")
private String firstname;
//Let's assume some getters and setters and a constructor
}
Entity AuthorOfBook:
#Entity
#Table(name="BOOK")
public class Book {
#EmbeddedId
private AuthorOfBookId pk;
}
An Embedded ID
#Embeddable
public class AuthorOfBookId implements Serializable {
private int authorId;
private int bookId;
}
Repository
#Repository
public interface AuthorOfBookRepository extends JpaRepository<,AuthorOfBookId> {
}
Now how would I represent that query:
SELECT b.name, a.firstname, a.lastname from AuthorOfBook ab inner join Book b on b.id = ab.book_id inner join Author a on a.id = ab.author_id where a.lastname = :lastname;
in my repository? I know the signature would need to be like
#Query([the query string from above])
public (...) findAuthorAndBookByAuthorLastname(#Param("lastname") String lastname);
but I cannot make out what Type the return would be like. What is that method returning? (simply AuthorOfBook would not work I guess)
You don't want AuthorOfBook as a separate Entity. Book should have a field of type Author as a #ManyToOne relationship. That way, given any Book, you can find the author's details.
If you want to handle audits fields you can do something like this:
Audit class
#Embeddable
public class Audit {
#Column(name = "created_on")
private Timestamp createdOn;
#Column(name = "updated_on")
private Timestamp updatedOn;
#Column(name = "is_deleted")
private Boolean isDeleted;
//getters and setters
}
AuditListener to update automatically audits fields
public class AuditListener {
private Long loggedUser = 1001L;
/**
* Method to set the fields createdOn, and isDeleted when an entity is persisted
* #param auditable
*/
#PrePersist
public void setCreatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
if (audit == null) {
audit = new Audit();
auditable.setAudit(audit);
}
audit.setIsDeleted(Boolean.FALSE);
audit.setCreatedOn(Timestamp.from(Instant.now()));
}
/**
* Method to set the fields updatedOn and updatedBy when an entity is updated
* #param auditable
*/
#PreUpdate
public void setUpdatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
audit.setUpdatedOn(Timestamp.from(Instant.now()));
}
}
And add this to the entities
#EntityListeners(AuditListener.class)
public class Book implements Auditable {
#Embedded
private Audit audit;

Hibernate querying on a parent class creates ineffective union query

I am using spring boot and hibernate and I have following data structure.
#Entity
public class Template {
#Id
private Integer id;
#OneToMany(mappedBy = "template", orphanRemoval = true, fetch = FetchType.EAGER,cascade = CascadeType.ALL)
#BatchSize(size = 30)
private Collection<TemplateContent> contents;
}
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class TemplateContent {
#Id
private String contentType;
#Id
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "template_id", nullable = false)
private Template template;
}
#Entity
public class SomeContent extends TemplateContent {
private String someValue;
}
#Entity
public class SomeOtherContent extends TemplateContent {
private String someOtherValue;
}
#Repository
public interface TemplateRepository extends JpaRepository<Template, Integer> {
Page<Template> findByIdIn(Collection<Integer> ids, Pageable pageable);
}
When I call findByIdIn method it generates the following query:
SELECT ...
FROM (SELECT content_type,
template_id,
some_value,
NULL AS some_other_value
FROM someContent
UNION
SELECT content_type,
template_id,
some_other_value,
NULL AS some_value
FROM someOtherContent) contents0_
WHERE contents0_.template_id IN ( ?, ?, ? )
which is ineffective, because MySQL can't use indexes over derived tables. Is there a way to generate a more effective query.
//this would be the desired query
SELECT ...
FROM (SELECT content_type,
template_id,
some_value,
NULL AS some_other_value
FROM someContent
WHERE template_id IN ( ?, ?, ? )
UNION
SELECT content_type,
template_id,
some_other_value,
NULL AS some_value
FROM someOtherContent
WHERE template_id IN ( ?, ?, ? )) contents_0_ ....
I tried also to use different inheritance strategies, but it seems like all of them have a similar drawback.
If you want to have best performance in polymorphic query the best approach is to use strategy = InheritanceType.SINGLE_TABLE, probably you will pay performance with empty columns, but it depends which aspect is more important for you, also because strategy = InheritanceType.JOINED has same problems of TABLE_PER_CLASS

JPA Join table with SELECT

I have a SQL query like this:
SELECT h.name, h.created_date, tbl.*
FROM my_table tbl
LEFT JOIN
(SELECT name, max(created_date) created_date FROM my_table GROUP BY name) h
ON tbl.name = h.name;
It returns the row from my_table (which has multiple for name="") along with the maximum created_date for that name.
Is there a way to replicate this in a JPQL query?
Here is the gist of the Entity class, it's quite simple:
#Entity
#Table(name = "MY_TABLE")
#XmlRootElement
public class MyTable implements Serializable {
private BigDecimal tableId;
private String name;
private Date createdDate;
// ...
#Id
#Basic(optional = false)
#Column(name = "TABLE_ID")
#GeneratedValue(generator = "TBL_ID_SEQ")
public BigDecimal getTableId() {
return tableId;
}
#Basic(optional = false)
#Column(name = "NAME")
public String getName() {
return name;
}
#Basic(optional = false)
#Column(name = "CREATED_DATE", insertable = false)
#Temporal(TemporalType.TIMESTAMP)
public Date getCreatedDate() {
return createdDate;
}
// ... getters/setters
}
Just reading your question I guess you do not need another entity. Entities in JPA are the same like tables in SQL. Usually there is a 1:1 relationship between entities and tables. You just have to know how to invoke a query using JPQ. You need a entity manager, which invokes your statement.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("PersistenceUnit");
EntityManager em = emf.createEntityManager();
You have to define your persistence unit, i.e. in a pom file, or a config java file. So done you can go on coding something like this:
Query q = em.createQuery( "Your query in sql syntax as a string object" );
In respect to your entities and invoked query you will receive a List using
List<object> resultOfMyQuery = q.getResultList();
This is only one short example. But hopefully you got some buzzwords to look for ;)

Categories

Resources