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
withreadOnly=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?