I want to use dependency inversion principle inside my book rental project. Before, I used AccountRepository that extends CrudRepository, so my method looked like this:
#Query("SELECT CASE WHEN COUNT(account) > 0 THEN true ELSE false END FROM
Account account WHERE account.id =:accountID")
boolean doesAccountExistsWithGivenID(#Param("accountID") int accountID);
I've created AccountRepository and class that implements this repository.
Class that implements interface is called PostgreSQLAccountRepository. And inside doesAccountExistsWithGivenID I want to query somehow to get same result.
It looks like this:
package bookrental.account;
import bookrental.bookrentals.BookRentals;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
#Repository
public class PostgreSQLAccountRepository implements AccountRepository {
private CrudRepository<Account, Integer> repository;
public PostgreSQLAccountRepository(CrudRepository<Account, Integer> repository) {
this.repository = repository;
}
#Override
public List<BookRentals> getAccountRentalsByGivenID(int accountID) {
//TODO
}
#Override
public void deleteById(Integer id) {
this.repository.deleteById(id);
}
#Override
public List<Account> findAll() {
return (List<Account>) this.repository.findAll();
}
#Override
public boolean doesAccountExistsWithGivenID(int accountID) {
//HERE I WANT TO USE JPQL
}
``}
I do not want to use existsByID, because I have a lot of methods that use JPQL so I need to know how to implement it inside the method.
The documentation is clear on how to customize methods from a Data repository:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations
Basically define the fragment of the interface you want to customize (CustomizedRepository). Extend this interface in your data repository
interface SomeRepositry extends CrudRepository<...>, CustomizedRepository
Create implementation for CustomizedRepository called CustomiyedRepositoryImpl. The Impl postfix is critical here. See the docs for more customizations.
You will need to autowire the SessionFactory and use it manually.
#Autowired
public setSessionFactory(EntityManagerFactory factory) {
if(factory.unwrap(SessionFactory.class) == null){
throw new NullPointerException("factory is not a hibernate factory");
}
this.hibernateFactory = factory.unwrap(SessionFactory.class);
}
After you have access to it, then you can use it directly
Session session = hibernateFactory.createSession();
Query query = session.createQuery("SELECT CASE WHEN COUNT(account) > 0 THEN true ELSE false END FROM Account account WHERE account.id =:accountID");
query.setParameter("accountId", "7277");
List list = query.list();
Related
I have a springboot application where I am saving a list with my Meeting Entity. I can save my entities all at once, but I want to check before each save if one entity inside my list already exists inside my Db and then just save the ones which are not inside. But I am kinda stuck and do not know how to do it. Could someone look at my code and give me an advice?
MeetingController:
#PostMapping("/")
public void saveMeeting(#RequestBody List<Meeting> meeting){
List<Meeting> exist =
meetingService.findAllMeetingsWithName(meeting.stream().map(m -> m.getMeetingName()).collect(Collectors.toList()));
meeting.removeAll(exist);
meetingService.saveMeeting(meeting);
}
MeetingService:
public void saveMeeting(List<Meeting> meeting){
meetingRepository.saveAll(meeting);
}
Repository:
#Repository
public interface MeetingRepository extends JpaRepository<Meeting, Long> {
}
This is the functionality that you want
#Transactional
public void saveMeeting(List<Meeting> meeting){
List<Meeting> alreadyExist = meetingRepository.findByMeetingNameIn(meeting.stream().map(m -> m.getMeetingName()).collect(Collectors.toList());
meeting.removeAll(alreadyExist);
meetingRepository.saveAll(meeting);
}
and then just define that repository method
#Repository
public interface MeetingRepository extends JpaRepository<Meeting, Long> {
List<Meeting> findByMeetingNameIn (List<String> meetingNames);
}
Remember to override equals and hashcode in Meeting entity to consider the id field.
You can check whether your meetings are in the DB. Try this.
List<Meeting> findByValueIn(List<Meeting> values);
Since you already are planning to use JpaRepository, take advantage of the #Query and batch queries
#Repository
//custom query using IN
public interface MeetingRepository extends JpaRepository<Meeting, Long>
{
#Query("SELECT m from MEETING m where m.name IN (:names)")
public List<Meetings> findAllMeetingWithNames(List<String> meetings)
}
#Service
class MeetingService{
private MeetingRepository meetingRepository;
...
//other code....
#Autowired
public MeetingService(MeetingRepository meetingRepository){
this.meetingRepository = meetingRepository;
}
public void saveAllMeetings(List<Meeting> meetings){
//call repository custom query here
names = meetings.stream()
.map((m) -> m.name)
.collect(Collectors.toList());
List<Meeting> existingMeetings = meetingRepository.findAllMeetingWithNames(names);
//delete using a single query
meetingRepository.deleteAllInBatch(existingMeetings);
meetingRepository.saveAllAndFlush(meetings);
}
.....
}
I found myself struggling to implement customizable methods in Spring Data JPA.
For example, I have a Pet class, which has an Owner(Many to One rel.) What if I have a method to save(Pet pet, int ownerId). How can I get ownerId? Using Hibernate I just can getReference like that
public Pet save(Pet pet, int ownerId) {
if (!pet.isNew() && get(pet.getId(), ownerId) == null) {
return null;
}
pet.setUser(em.getReference(Owner.class, ownerId));
if (pet.isNew()) {
em.persist(pet);
return pet;
} else {
return em.merge(pet);
}
}
But using a Spring DJPA it's not so easy. I've created an interface that extends JpaRepository < Pet, Integer >, hoping that the parent class has a method called
saveWithReference, but i didn't find anything.. Any ideas guys?
You should have both a PetRepository and OwnerRepository both extending JpaRepository.
public interface PetRepository extends JpaRepository<Pet, Long> {}
and
public interface OwnerRepository extends JpaRepository<Owner, Long> {}
Using Spring Data JPA you can use the getOne method to get a reference, this in contrast to the findOne which will actually query the database.
The code you wrote using the EntityManager is basically the same and you should put that in a service method and instead of directly using the EntityManager use the 2 repositories.
#Service
#Transactional
public PetService {
private final PetRepository pets;
private final OwnerRepository owners;
public PetService(PetRepository pets, OwnerRepository owners) {
this.pets=pets;
this.owners=owners;
}
public Pet savePet(Pet pet, long ownerId) {
if (!pet.isNew() && get(pet.getId(), ownerId) == null) {
return null;
}
pet.setUser(owners.getOne(ownerId));
return pets.save(pet);
}
}
Something like that should do the trick. NO need to implement methods in your repository.
I know this question has been asked often but I'm unable to find a solution.
How can I get a generic type class name in a Spring injected repository?
Here it is my base repository
public interface UserRepository extends JpaRepository<User, Long>, IUserRepository<User>{
User findByUsername(String username);
}
this is the interface
public interface IUserRepository<T> {
public List<T> findAllValidEtSiteAndStructure();
}
and finally here it is the implementation
public class UserRepositoryImpl<T> implements IUserRepository<T> {
#PersistenceContext
private EntityManager em;
private Class< T > type;
#Override
public List<T> findAllValidEtSiteAndStructure() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof UserAuthentication) {
final User currentUser = ((UserAuthentication) authentication).getDetails();
return (List<T>) em.createQuery("FROM " + type.getName()+ " WHERE site=:site AND structure=:structure AND valid=:valid")
.setParameter("site", currentUser.getInstallation().getSite())
.setParameter("structure", currentUser.getInstallation().getStructure())
.setParameter("valid", true)
.getResultList();
}
return null;
}
}
how can I get type.name?
Thanks in advance
Considering that you're using Spring Framework, use the code snippet bellow, I've tested and it worked just fine:
ResolvableType resolvableType = ResolvableType.forClass(UserRepository.class).as(JpaRepository.class);
System.out.println(resolvableType.getGeneric(0));//User
System.out.println(resolvableType.getGeneric(1));//Long
Basically you can't get the generic type because of type erasure.
What I would do is add an abstract method to UserRepositoryImpl that returns the relevant type:
public abstract Class getType();
And then I would create specific instances for UserRepositoryImpl for which the type is already known at compile time. For example:
public class StudentRepository extends UserRepositoryImpl<Student> {
public Class getType() {
return Student.class;
}
}
The general answer to you question can be seen in the documentation -> http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations in the chapter Custom implementations for Spring Data repositories
But I think that should not be necessary in your case. You should be able to do it in the following way.
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
List<User> findByStructureAndSiteAndValid(Structure structure, Site site, boolean valid);
}
I'm using Spring Data JPA and I have a bunch of repositories like this one:
public interface CustomerRepository extends JpaRepository<Customer, Long> {}
Under repositories I have services and a lot of them need to have implemented method findOrCreate(String name) like this one:
#Override
#Transactional
public List<Customer> findOrCreate(final String name) {
checkNotNull(name);
List<Customer> result = this.customerRepository.findByName(name);
if (result.isEmpty()) {
LOGGER.info("Cannot find customer. Creating a new customer. [name={}]", name);
Customer customer = new Customer(name);
return Arrays.asList(this.customerRepository.save(customer));
}
return result;
}
I would like to extract method to the abstract class or somewhere to avoid implementing it for each services, testing and so on.
Abstract class can be look like this:
public abstract class AbstractManagementService<T, R extends JpaRepository<T, Serializable>> {
protected List<T> findOrCreate(T entity, R repository) {
checkNotNull(entity);
checkNotNull(repository);
return null;
}
}
And the problem is it due to the fact that I need to find object by name as a string before creating a new one. Of course interface JpaRepository doesn't offer this method.
How can I solve this problem?
Best Regards
Create a custom JpaRepository implementation that includes this behaviour. See this post for an example of writing a custom JpaRepository implementation.
I am extending CrudRepository in my Repository class. I want to print the records in my table using the findAll method. So far, I have written a test class, and I can see the result query is correct. How can I print the individual records in the table?
Here is a snippet of my code:
Repository Class
public interface RepositoryAda extends CrudRepository{
}
Service Class
#Service
public class Service{
#Autowired private RepositoryAda repository;
#Transactional
public List selectRecords(){
return (List) repository.findAll();
}
}
Test Case:
#Test
public void getAllRecords() {
service.selectRecords();
}
How can I print the individual records from the table to a console?
I prefer to use Google's Guava when using the repository interfaces. You can turn findAll() Iterable into a List<Type> with one line.
public RecordRepository extends CrudRepository<Record, Long> {}
public class RecordServiceImple implements RecordService {
RecordRepository recordRepository;
public List<Record> selectRecord() {
return Lists.newArrayList(recordRepository.findAll()); // Guava library
// or just simply cast it.
// return (List<Record>)recordRepository.findAll();
}
}
Then just loop through the list
for (Record record : records) {
System.out.println(record);
}
Just overrive the toString() in your Record class, or whatever your class name is, to tabular formatting using String.format()