Common spring jpa repository with multiple entity finders - spring

My application has over 250 tables with each having ID and name columns. I am trying to migrate our application from hibernate 3 to Spring-JPA 4.3 with hibernate 5+.
In my current hibernate layer I have (Option 1):
public class DAO
{
private Session session;
public DAO(Session session)
{
this.session=session;
}
public EntityA findById(String id)
{
//implementation
return entityA;
}
public EntityB findByName(String name)
{
//implementation
return entityB;
}
public EntityC findByIdAndName(String id, String name)
{
//implementation
return entityC;
}
}
Back in the days i could have done the following with more generic methods but I didn't want to reinitialize this class if i have 10 different entities to fetch by ID.
public class DAO<T>
{
public T findById(String id)
{
//implementation
return T;
}
public T findByName(String name)
{
//implementation
return T;
}
public T findByIdAndName(String id, String name)
{
//implementation
return T;
}
}
Now how can i achieve this in Spring-JPA. So If i need to get 10 different entities by ID, i don't want to initialize 10 repositories, i want to have one repository that i can use to fetch any entity i want byId or byName or byIDAndName. I could do it easily with JdbcTemplate but that means it may not be tracked by JPA/hibernate caching mechanism.
So how can do the following in one JPA repository:
{
#Query("from EntityA where id=?1")
EntityA findEntityAById(String id);
#Query("from EntityB where name=?1")
EntityB findEntityBById(String name);
#Query("from EntityC where id=?1 and name=?2")
EntityC findEntityCById(String id,String name);
}

You should be able to create a super class(es) that has the common attributes, mark it as a #MappedSuperClass and create a repository for that super class as #NoRepositoryBean. You'll just have to do some casting for your results.
See this answer

Related

Hibernate batch delete with quarkus.hibernate-orm.jdbc.statement-batch-size not working?

According to the Quarkus docs, including the line below in the application.properties should result in delete statements to be batched.
quarkus.hibernate-orm.jdbc.statement-batch-size=1000
However, I can't get this to work. Regardless of this property all delete statements are sent to the database individually instead of in batches.
Is there anything else I need to do?
To reproduce, use a simple entity like this:
#Entity
#Table(name = "book")
public class Book {
#GeneratedValue(strategy = IDENTITY)
#Id
private Long id;
private String title;
public Book() {
}
public Long getId() {
return id;
}
}
insert records into the database like this (on PostgreSQL):
INSERT INTO book (id, title)
VALUES(generate_series(1, 200), 'a title');
and a simple integration test like this:
#QuarkusTest
class BookDeleteIT {
#Inject EntityManager em;
#Test
void deletes_records_in_batches() {
List<Book> books = getBooks();
deleteBooks(books);
}
#Transactional
List<Book> getBooks() {
return em.createQuery("SELECT b FROM Book b").getResultList();
}
#Transactional
void deleteBooks(List<Book> books) {
books.forEach(book -> delete(book));
}
private int delete(Book book) {
return em.createQuery("DELETE FROM Book b WHERE b.id = :id")
.setParameter("id", book.getId())
.executeUpdate();
}
}
When I run this test, the deletes are sent to the database individually instead of in batches.
I suppose the error is the way you're deleting a book: use em.remove(book) instead of the query and Hibernate will accumulate deletions.
There are possibilities that deleting a managed entity using a query instead of EntityManager prevents your JPA provider (Hibernate) to manage entity lifecycle and do some optimizations (like batch deletion).

Spring JPA repository find all which does not exist

I have a repository
public interface PersonRepository extends JpaRepository<Person, Long> {}
and the Entity looks like this:
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
public class Person {
#Id
private Long id;
#NotBlank
private String name;
}
I want to have a method which checks if all "persons" exist in database table by id, this what I have so far:
void checkIfAllPersonsExist(List<Long> personIds) {
var persons = personRepository.findAllById(personIds);
if (personIds.size() != persons.size()) {
personIds.removeAll(persons.stream().map(Persons::getId).collect(toList()));
throw new NotFoundException("Persons with id's [id:%s] does not exist", personIds);
}
}
I wonder if Spring JPA Repository can provide anything more elegant? Like specific named query which returns id's which does not exist?
If you want to just know that there are some ids that not exist you can count them
#Query("select COUNT(p.id) from Person p where p.id in :ids")
Long countIds(List<Long> ids);
Or equivalent based on
long countByIdIn(Collection<Long> ids);
Or return list of ids that exists
#Query("select p.id from Person p where p.id in :ids")
List<Long> getExistenIds(List<Long> ids);
And then filter out what you need.
personIds.removeAll(personRepository.getExistenIds(personIds));
if (!personIds.isEmpty()) {
throw new NotFoundException("Persons with id's [id:%s] does not exist", personIds);
}
First of all, your repository should extend JpaRepository<Person, Long> instead of JpaRepository<Person, String >, because your entity's id type is Long.
In and NotIn keywords can help you to achive your goal. Please check them out in this document: Query Creation - Spring Data JPA - Reference Documentation
I modified your code a little bit and it works for me.
Repository class:
public interface PersonRepository extends JpaRepository<Person, Long> {
List<Person> findByIdIn(Collection<Long> ids);
}
And sample snippet:
#Component
public class Bootstrap implements CommandLineRunner {
#Autowired
private PersonRepository repository;
#Override
public void run(String... args) throws Exception {
savePersons();
testFindMethod();
}
private void savePersons() {
Person person1 = Person.builder().id(1L).name("Name 1").build();
Person person2 = Person.builder().id(2L).name("Name 2").build();
Person person3 = Person.builder().id(3L).name("Name 3").build();
Person person4 = Person.builder().id(4L).name("Name 4").build();
repository.save(person1);
repository.save(person2);
repository.save(person3);
repository.save(person4);
}
private void testFindMethod() {
List<Long> toFind = new ArrayList<>();
toFind.add(1L);
toFind.add(2L);
toFind.add(3L);
checkIfAllPersonsExist(toFind);
toFind.add(7L);
checkIfAllPersonsExist(toFind);
}
void checkIfAllPersonsExist(List<Long> personIds) {
List<Person> persons = repository.findByIdIn(personIds);
if (personIds.size() != persons.size()) {
System.out.println("Sizes are different");
} else {
System.out.println("Sizes are same!");
}
}
}
And this is console output:
Sizes are same!
Sizes are different
I hope this will help you.
With this JPA repository method you can get the elements which ids doesn't exists:
List<Person> findByIdNotIn(List<Long> personIds);
If you want to remove them like in your example, you can use this one:
List<Person> deleteByIdNotIn(List<Long> personIds);
I hope it helps!

Fetch selected columns from two or more table using Spring Jpa

#Entity
public class A{
//some properties
}
#Entity
public class B{
//Some properties
}
I want to fetch selected columns from two tables using JPA, I know how to fetch single Entity table data through Repository and Controllers.
Repository:
public interface extends JPARepository<A, Long>{
List<A> findAll();}
Controller:
public class class_name{
#AutoWired
private JPARepository repo;
#RequestMapping("/data")
public List<A> getData(){
return repo.findAll();
}
}
Above code is to fetch single table data. Now, I want to fetch selected columns from both the tables.
Note: A, B Entities have mappings
What you can do is to use #Query annotation on one of your methods in the repository and performs something like this:
public Name {
String firstName;
String telephone;
public Name(String firstName,String telephon) {
//initialize fields
}
}
#Query(select new dummy.Name(u.name,c.telephone) from User u join fetch u.contact c where u.externalId= ?1 )
public Name getName(String externalId){}
You can return easily List instead of using constructor query , but i find it cleaner this way.

Query and Database performance when used with JpaRepository findAll() vs native query using JpaRepository

I am developing a spring boot project where i am having two functions for JPA on which i need to figure out which function will perform better and put less pressure on database query performance and utilise Hibernate caching. Please guide on which query to use.
My Repository interface:
#Repository
public interface CustomersRepository
extends JpaRepository<CustomersEntity, Long> {
#Query(nativeQuery = true, value = "SELECT * FROM customers WHERE c_mobile = ?1")
CustomersEntity findcustomerByMobile(String mobileNo);
#Override
List<CustomersEntity> findAll();
}
My Service class:
#Scope("request")
#Service
public class CustomerServiceImpl implements ICustomerService {
#Autowired
private CustomersRepository customersRepository;
#Override
public boolean findCustomerByMobile1(long mobileNo) {
CustomersEntity customersEntity = customersRepository.findcustomerByMobile(mobileNo);
if (customersEntity != null)
return true;
else
return false;
}
#Override
public boolean findCustomerByMobile2(long mobileNo) {
List<CustomersEntity> entityList = customersRepository.findAll();
for (CustomersEntity entity : entityList) {
if (entity.getcMobile() == mobileNo) {
return true;
}
}
return false;
}
}
There is no need to download all records from the database to your app and then filtering them. With thousands of records it will slow down.
Instead you should create an index on c_mobile field then use just like this simple method:
public interface CustomerRepo extends JpaRepository<CustomersEntity, Long> {
CustomersEntity findByMobileNo(String mobileNo);
}
It will work in a flash (with index).
More info about building query methods you can find here.

Spring data JPA locking

I need to use #Lock inside of my implementations:
#Lock(LockModeType.PESSIMISTIC_WRITE)
private Note findOneForUpdate(BigInteger id) {
return noteDao.findOne(id);
}
But other sources say it should be in interfaces:
#Repository
public interface NoteRepository extends JpaRepository<Note, BigInteger>, NoteDao {
#Lock(LockModeType.PESSIMISTIC_WRITE)
Note findOne(BigInteger id);
}
So, is first option possible? I tried it with spring-boot-starter-data-jpa 1.5.3.RELEASE, but lock did not work.
#Lock annotation is required in repository class
#Lock(LockModeType.PESSIMISTIC_WRITE) // not required
private Note findOneForUpdate(BigInteger id) {
return noteDao.findOne(id);
}
#Repository
public interface NoteRepository extends JpaRepository<Note, BigInteger>, NoteDao {
#Lock(LockModeType.PESSIMISTIC_WRITE) // required
Note findOne(BigInteger id);
}

Resources