1

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:

  1. 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?
  2. 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). look at. Now I am confused whether I should use @Transactional with readOnly=true or not.
  3. 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?
  4. Have I called queries(methods) in proper format?
  5. Is this problem of hibernate or spring data jpa?
  6. When do we need TestEntityManager? Is it required when we use spring data jpa?
swapyonubuntu
  • 1,952
  • 3
  • 25
  • 34
  • 1
    I would remove the readonly and call the save method after the 3 setters. – Robert Niestroj Jan 19 '17 at 21:50
  • That means just setters never fire SQL update... – swapyonubuntu Jan 20 '17 at 02:49
  • Please ask only one question at a time and those too with specific code related problems; otherwise questions become too broad to answer. The official JPA, Spring Data, Spring Data JPA and Hibernate documentation is otherwise sufficient to resolve all conceptual queries (you just need to spend time reading through them). – manish Jan 21 '17 at 04:59
  • To your first question, read the [JPA spec](http://download.oracle.com/otn-pub/jcp/persistence-2_1-fr-eval-spec/JavaPersistence.pdf) (section 3.2.4). Entities are synchronized: `a`. if a transaction is committed; or `b`. `flush` is called; or `c`. if the persistence provider believes it should flush changes (for example, if a `SELECT` query needs to be executed on a context which has dirty entities). Persistence providers are not required to save entities when simply setters are called on them. – manish Jan 21 '17 at 05:00
  • To your second question, `@Transactional(readOnly = true)` is useful to prevent accidental writes during operations where writes are not expected. However, the underlying JDBC driver may not honour it at all times. Also see [this post](http://stackoverflow.com/questions/34797480/why-do-spring-hibernate-read-only-database-transactions-run-slower-than-read-wri) for the performance overhead of `@Transactional(readOnly = true)` in some situations. – manish Jan 21 '17 at 05:10
  • To your third question, use `@NoRepositoryBean` when you are not going to use an intermediate repository. See [Spring Data JPA documentation](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.definition-tuning). – manish Jan 21 '17 at 05:12
  • The answers to the fourth and fifth questions lie in the answer to the first question. – manish Jan 21 '17 at 05:13
  • To the sixth question, what is `TestEntityManager`? If it is an implementation of `EntityManager`, it should not be injected into Spring managed beans since the purpose of dependency injection is defeated. – manish Jan 21 '17 at 05:15
  • I think the questions are interrelated.. so i have coupled them. I dont think test related questions can exist without showing proper data jpa repositories. – swapyonubuntu Jan 23 '17 at 06:22
  • @manish we cannot create instance of TestEntity manager on our own. I tried and spring example also mentions autowiring of TestEntityManger.https://spring.io/blog/2016/04/15/testing-improvements-in-spring-boot-1-4#testing-the-jpa-slice – swapyonubuntu Jan 23 '17 at 09:42

0 Answers0