Spring Data JPA save() return entity - java

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);
}
}

Related

"Column not found" when joining table in SpringBoot

I'm new in spring boot and now I'm trying to joining two tables where one of them are composite table (idk if it's have any effect or not). Please take a look on my code and help me.
Employees.java (Model)
#Entity
#Table(name = "employees")
public class Employees {
#Id
#Column(name = "emp_no")
private long emp_no;
#Column(name = "name")
private String name;
}
Salaries.java (Model, The composite table)
#Entity
#Table(name = "salaries")
public class Salaries {
#Column(name = "emp_no") //Primary Key
private long emp_no;
#Column(name = "salary")
private int salary;
#Column(name = "date") //Primary Key
private LocalDate date;
}
EmployeeRepo.java (Repository)
public interface EmployeesRepository extends JpaRepository<Employees, Long> {
String salary = "SELECT employees.name, salaries.salary FROM employees INNER JOIN salaries ON employees.emp_no=salaries.emp_no LIMIT 0,10";
#Query(value=salary, nativeQuery = true)
List<Employees> getUserSalary();
}
EmpController.java (Controller)
#GetMapping("/salary")
public ResponseEntity<List<Employees>> getSalaries(#RequestParam(required = false) String args){
List<Employees> employees = new ArrayList<Employees>();
employeesRepository.getUserSalary().forEach(employees::add);
if(employees.isEmpty()){
return new ResponseEntity<>(employees, HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(employees, HttpStatus.OK);
}
application.properties
spring.datasource.url= jdbc:mysql://127.0.0.1:3306/employees?useSSL=false
spring.datasource.username= ""
spring.datasource.password= ""
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto= update
And the error message:
Column 'emp_no' not found.
I dont understand why I get this error when I have emp_no column both on my tables in db.
I guess your problem is
String salary = "SELECT employees.name, salaries.salary FROM employees INNER JOIN salaries ON employees.emp_no=salaries.emp_no LIMIT 0,10";
#Query(value=salary, nativeQuery = true)
List<Employees> getUserSalary();
you are trying to save name,salary in Employees which only has emp_no, name. So the error is sating you have mapped name but emp_no is not available from query.
Create POJO/model like below
public class EmpSalary {
private String name;
private double salary;
}
Then update your repository to return EmpSalary
String salary = "SELECT employees.name, salaries.salary FROM employees INNER JOIN salaries ON employees.emp_no=salaries.emp_no LIMIT 0,10";
#Query(value=salary, nativeQuery = true)
List<EmpSalary> getUserSalary();

Hibernate returns null for entity property that is annotated with #Formula after saving the entity

I have simple class as you can see below:
#Entity
#Table(name = "post")
public class Post {
#Id
#GeneratedValue
Long id;
#Column(name = "title")
String title;
#Formula("(select current_date())")
Date currentDate;
#OneToMany(mappedBy = "post")
Set<PostComment> commentList = new HashSet<>();
}
and want to update this entity in the service:
#Service
#Transactional
public class PostService {
private final PostRepository postRepository;
public PostService(PostRepository postRepository) {
this.postRepository = postRepository;
}
#Transactional
public Post save(Post entity) {
Post post = postRepository.saveAndFlush(entity);
System.out.println("current_date: " + post.getCurrentDate());
post.getCommentList().forEach(pc -> System.out.println(pc.getReview()));
return post;
}
}
And when i check hibernate logs, it first select all fields of Post entity but when i call post.getCurrentDate() (that it annotated with #Formula) returns null:
Hibernate: select post0_.id as id1_0_0_, post0_.title as title2_0_0_, (select current_date()) as formula0_0_ from post post0_ where post0_.id=?
Hibernate: update post set title=? where id=?
current_date: null
Hibernate: select commentlis0_.post_id as post_id3_1_0_, commentlis0_.id as id1_1_0_, commentlis0_.id as id1_1_1_, commentlis0_.post_id as post_id3_1_1_, commentlis0_.review as review2_1_1_ from post_comments commentlis0_ where commentlis0_.post_id=?
review
Why it returns and logs commentList but doesn't return currentDate? is it hibernate bug?
NOTE
I pushed the complete sample project in the github you can see here.
I wrote a test case on my high-performance-java-persistence GitHub repository which works like a charm.
The entity looks as follows:
#Entity(name = "Event")
#Table(name = "event")
public static class Event {
#Id
private Long id;
#Formula("(SELECT current_date)")
#Temporal(TemporalType.TIMESTAMP)
private Date createdOn;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getCreatedOn() {
return createdOn;
}
public void setCreatedOn(Date createdOn) {
this.createdOn = createdOn;
}
}
Notice that the Date property uses the #Temporal annotation as well.
Now to emulate your use case, I wrote the following data access logic:
Event event = new Event();
event.setId(1L);
entityManager.persist(event);
entityManager.flush();
entityManager.refresh(event);
assertNotNull(event.getCreatedOn());
Notice the refresh call which is needed since the entity is cached upon persist, and we want to refetch it anew from the DB.
And, when executing it, Hibernate generates the following statements:
Query:["insert into event (id) values (?)"], Params:[(1)]
Query:["select formulacur0_.id as id1_0_0_, (SELECT current_date) as formula0_0_ from event formulacur0_ where formulacur0_.id=?"], Params:[(1)]
And the test case runs just fine. Just do a comparison debug between your use case and mine and see why yours doesn't work and mine does.

Spring Data JPA does not fire update query on dirty context

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?

DataIntegrityViolationException persisting one to many relation

Boot,Jpa and hibernate to persist a one-many relation between venues and events.
The error i'm retrieving is
org.h2.jdbc.JdbcSQLException: NULL not allowed for column "VENUE_LOCATION"; SQL statement:
insert into event (id, date, description, media_ref, num_ratings, performer, performer_media, title, total_rating) values (null, ?, ?, ?, ?, ?, ?, ?, ?) [23502-192]
I've tried saving the parent(Venue) class first and exclusively but that produces the same error.
Venue
public class Venue
{
#Id
private String name;
#Id
private String location;
#OneToOne(mappedBy = "venue",cascade = CascadeType.ALL)
#JoinColumn(name="id")
private VenueUser venueUser;
private String mediaRef;
private int rating;
#OneToMany(fetch=FetchType.LAZY,mappedBy = "venue",cascade = CascadeType.ALL)
private Set<Event> events;
//Constructors getters and setters below
Event
#Entity
public class Event
{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
private String title;
private String description;
private String performer;
private String[] performerMedia;
private Calendar[] date;
#Transient
private double avgRating;
private int numRatings;
private int totalRating;
private String mediaRef;
#MapsId("name")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name="Venue_name",referencedColumnName = "name"),
#JoinColumn(name="venue_location",referencedColumnName = "location")
})
private Venue venue;
//Constructors getters and setters below
Controller
#RequestMapping(value = "/event",method=RequestMethod.POST)
public ResponseEntity addEvent(#RequestBody Event event)
{
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName(); //get logged in username
Venue venue = userVenueRepository.findByEmail(name).getVenue();
event.setVenue(venue);
venue.addEvent(event);
if(eventRepository.saveAndFlush(event).equals(event)&&venueRepository.saveAndFlush(venue).equals(venue))
{
return new ResponseEntity(HttpStatus.CREATED);
}
else
{
return new ResponseEntity(HttpStatus.CONFLICT);
}
}
insert into event (id, date, description, media_ref, num_ratings, performer, performer_media, title, total_rating) values (null, ?, ?, ?, ?, ?, ?, ?, ?)
You need to set id to your Event entity. Better use #GeneratedValue annotation with AUTO, like here https://github.com/spring-projects/spring-data-jpa/blob/master/src/main/java/org/springframework/data/jpa/domain/AbstractPersistable.java
or use class AbstractPersistable as parent entity.
Error says that location field of Venue entity is null which cannot be as it is primary key.
You have two options for persisting Event object.
First persist Venue [Parent] Entity and then Persist as many Event [Child] entities as you want.Set venue field in event entity. You need to save Parent entity only once.
If you want to persist both parent and child at once, you can specify cascade = CascadeType.PERSIST in Event Entity and then save child entity.
Ok so I managed to fixed this and in hindsight I shouldn't of blindly followed a tutorial, I wasn't to sure what the #MapsId did so I removed it and everything started working.If anyone could explain what #MapsId does and why it was causing problems that would be appreciated.
You can try this too.
this way you don't need to add parent entry inside child object.
Remove mappedBy form Venue entity
Then add below code inside the Venue entity before Set<Event>
#JoinColumns({
#JoinColumn(name="Venue_name",referencedColumnName = "name"),
#JoinColumn(name="venue_location",referencedColumnName = "location")
})
Remove #JoinColumns and #MapsId from Event entity
Then you don't need to write
event.setVenue(venue);
Hope it helps.

hibernate annotation- inheritance with #MappedSuperclass - values are not being set to base class fields- strange error

I was following Hibernate: Use a Base Class to Map Common Fields and openjpa inheritance tutorial to put common columns like ID, lastModifiedDate etc in base table.
My annotated mappings are as follow :
BaseTable :
#MappedSuperclass
public abstract class BaseTable {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "lastmodifieddate")
private Date lastModifiedDate;
...
Person table -
#Entity
#Table(name = "Person ")
public class Person extends BaseTable implements Serializable{
#Column(name = "name")
private String name;
...
Create statement generated :
create table Person (id integer not null auto_increment, lastmodifieddate datetime, name varchar(255), primary key (id)) ;
After I save a Person object to db,
Person p = new Person();
p.setName("Test");
p.setLastModifiedDate(new Date());
..
getSession().save(p);
I am setting the date field but, it is saving the record with generated ID and LastModifiedDate = null, and Name="Test".
Insert Statement :
insert into Person (lastmodifieddate, name) values (?, ?)
binding parameter [1] as [TIMESTAMP] - <null>
binding parameter [2] as [VARCHAR] - Test
Read by ID query :
When I do hibernate query (get By ID) as below, It reads person by given ID.
Criteria c = getSession().createCriteria(Person.class);
c.add(Restrictions.eq("id", id));
Person person= c.list().get(0);
//person has generated ID, LastModifiedDate is null
select query select person0_.id as id8_, person0_.lastmodifieddate as lastmodi8_, person0_.name as name8_ from Person person0_
- Found [1] as column [id8_]
- Found [null] as column [lastmodi8_]
- Found [Test] as column [name8_ ]
ReadAll query :
Query query = getSession().createQuery("from " + Person.class.getName());
List<Person> allPersons=query.list();
Corresponding SQL for read all
select query select person0_.id as id8_, person0_.lastmodifieddate as lastmodi8_, person0_.name as name8_ from Person person0_
- Found [1] as column [id8_]
- Found [null] as column [lastmodi8_]
- Found [Test] as column [name8_ ]
- Found [2] as column [id8_]
- Found [null] as column [lastmodi8_]
- Found [Test2] as column [name8_ ]
But when I print out the list in console, its being more weird. it is selecting List of Person object with
ID fields = all 0 (why all 0 ?)
LastModifiedDate = null
Name fields have valid values
I don't know whats wrong here. Could you please look at it?
FYI,
My Hibernate-core version : 4.1.2, MySQL Connector version : 5.1.9 .
In summary, There are two issues here
Why I am getting All ID Fields =0 when using read all?
Why the LastModifiedDate is not being inserted?
Use
#Temporal(TemporalType.DATE)
annotation on Date
For ID generation use strategy.
#GeneratedValue(strategy=GenerationType.AUTO)
or
#GeneratedValue(strategy=GenerationType.IDENTITY)
or using sequance
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "custom_generator")
#SequenceGenerator(name = "custom_generator", sequenceName = "sequance_table")
also you can make property access in your child class:
public abstract class BaseTable {
protected int id;
protected Date lastModifiedDate;
// ...
}
and
#Entity
#Table(name = "Person")
public class Person extends BaseTable implements Serializable{
#Id
#GeneratedValue
#Column(name = "id")
public getId()
{
return super.id;
}
setId(log id)
{
super.id = id;
}
#Column(name = "lastmodifieddate")
#Temporal(TemporalType.DATE)
public getLastModifiedDate()
{
return super.lastModifiedDate;
}
setLastModifiedDate(Date date)
{
super.lastModifiedDate = date;
}
public getName()
// ...
}
Try this out in BaseTable class
#Temporal(value = TemporalType.TIMESTAMP)
#Column(name = "lastmodifieddate", nullable = false,
columnDefinition = "Timestamp default CURRENT_TIMESTAMP")
#org.hibernate.annotations.Generated(value = GenerationTime.ALWAYS)
public Date getLastModifiedDate( )
{
return this.lastModifiedDate;
}
where BaseTable class should have #MappedSuperclass. And there must be one generated date field in an entity.
#MappedSuperclass
public abstract class BaseTable {
protected int id;
protected Date lastModifiedDate;
#Id
#GeneratedValue(strategy = GenerationType.AUTO) // For Mysql
// For Oracle or MSSQL #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
public int getId()
{
return this.id;
}
#Temporal(value = TemporalType.TIMESTAMP)
#Column(name = "lastmodifieddate", nullable = false,
columnDefinition = "Timestamp default CURRENT_TIMESTAMP")
#org.hibernate.annotations.Generated(value = GenerationTime.ALWAYS)
public Date getLastModifiedDate( )
{
return this.lastModifiedDate;
}
}

Categories

Resources