Alternatives to getReference method in Spring data JPA - java

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.

Related

How to use JPQL without #Query annotation inside the method?

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

How to make controller endpoint to get two different objects in java spring?

I have a server built with java and spring.
What i am trying to do is that my controller with the same endpoint will get two different objects.
This is an example for what I mean:
I know I can do that:
public class Option1{
private String name;
...
//getter and setter
}
public class Option2{
private Long id;
...
//getter and setter
}
#Controller
public class Controller{
#RequestMapping(value = "service/getData/option1", method = RequestMethod.POST)
#ResponseBody
public String searchProv(#ResponseBody Option1 data1){
return "option1"
}
#RequestMapping(value = "service/getData/option2", method = RequestMethod.POST)
#ResponseBody
public String searchProv(#ResponseBody Option2 data2){
return "option2"
}
}
but I wonder if it is possible to passing different json object to the same endpoint and do that:
#Controller
public class Controller{
#RequestMapping(value = "service/getData", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<Any> getData(#ResponseBody Option1And2 data){
if(data instanceof Option1){
return return ResponseEntity<Any>(data.name,HttpStatus.OK)
}
if(data instanceof Option2){
return ResponseEntity<Any>(data.id,HttpStatus.OK)
}
return ResponseEntity<Any>("ok",HttpStatus.OK)
}
such that 'Option1And2' is generic object can be option1 or option2.
I tried to replace 'Option1And2' to 'Any' but it didn't went well because I get a list of keys and values
You should use JsonNode object.
for your example you should do this:
#Controller
public class Controller{
#RequestMapping(value = "service/getData", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<Any> getData(#RequestBody JsonNode jsonNode){
ObjectMapper obj = new ObjectMapper();
if(jsonNode.has("name"){
Option1 result= obj.convertValue(jsonNode,Option1.class)
return ResponseEntity<Any>(result.name,HttpStatus.OK)
}
else {
Option2 result= obj.convertValue(jsonNode,Option2.class)
return ResponseEntity<Any>(result.id,HttpStatus.OK)
}
return ResponseEntity<Any>("ok",HttpStatus.OK)
}
the JsonNode and the ObjectMapper you should import from here:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.JsonNode;
this link should help you to understand better on JsonNode and give you more details.
and this link should help you with the convertValue from JsonNode to java object(POJO).
This is a good time to use inheritance and Java Generics. It is worth noting, if your controller has any dependencies such as a #Service or #Repository, then those too must be generic.
You might have a generic controller:
abstract class GenericController<T> {
public abstract GenericService<T> getService();
#GetMapping
public ResponseEntity<Iterable<T>> findAll() {
return ResponseEntity.ok(getService().findAll());
}
#PostMapping
public ResponseEntity<T> save(T entity) {
return ResponseEntity.ok(getService().save(entity));
}
// #DeleteMapping, #PutMapping
// These mappings will automatically be inherited by
// the child class. So in the case of findAll(), the API
// will have a GET mapping on /category as well as a GET
// mapping on /product. So, by defining and annotating the
// CRUD operations in the parent class, they will automatically
// become available in all child classes.
}
#Controller
#RequestMapping("/category")
class CategoryContr extends GenericController<Category> {
#Autowired CategoryServ serv;
#Override
public GenericService<Category> getService() {
return serv;
}
}
#Controller
#RequestMapping("/product")
class ProductContr extends GenericController<Product> {
#Autowired ProductServ serv;
#Override
public GenericService<Product> getService() {
return serv;
}
}
You then have to have abstract versions of the dependencies. The services:
abstract class GenericService<T> {
public abstract GenericRepository<T> getRepository();
public Iterable<T> findAll() {
return getRepository().findAll();
}
public T save(T entity) {
return getRepository().save(entity);
}
}
#Service
class CategoryServ extends GenericService<Category> {
#Autowired CategoryRepo repo;
#Override
public GenericRepository<Category> getRepository() {
return repo;
}
}
#Service
class ProductServ extends GenericService<Product> {
#Autowired ProductRepo repo;
#Override
public GenericRepository<Product> getRepository() {
return repo;
}
}
Then, the services have their dependencies as well - the repositories:
#NoRepositoryBean
interface GenericRepository<T> extends JpaRepository<T, Long> {
}
#Repository
interface CategoryRepo extends GenericRepository<Category> {
}
#Repository
interface ProductRepo extends GenericRepository<Product> {
}
This was my first approach. It works very nicely. However, this does create a strong coupling between the business logic of each service and the generic service. The same holds true for the generic controller and its child classes. You can of course always override a particular CRUD operation. But, you must do this with care as you may created unexpected behavior. It is also worth noting that inheriting from classes that have methods that are annotated with #RequestMapping automatically exposes all of the annotated methods. This may be undesirable. For example, we may not want a delete option for categories, but we want it for products. To combat this, instead of annotating the method in the parent class, we can simply define it in the parent class, and override the desired CRUD operations with the added #RequestMapping annotation and then call the super class method.
Another approach is using annotations.
Seems like you want program itself to determine what type the option is.But before you do that,are you sure what is the difference between these two Object?
First is,what is the Option1And2 actually is?If the Option1And2 contains all the field of Option1 and Option2 but it's not the subclass of those,then probably the Option1And2 could be like:
#Data
public class Option1And2{
private String name;
private Long id;
}
If you have other limits like "one of them and only one of them has
to be null",then you could determine it by this rule.
If you don't have any other limitation,then maybe you could add a new
field as a flag.
In fact those code style are not recommend.If those two functions have different responsibilities,then maybe it's better to not mix them together.You will understand what I mean when you have to refactor these code.
If these two functions do have lots of things in common,maybe it's better for you to refactor the service logic instead of just combining two service roughly by creating a new param Option1And2.
By the way,what are you exactly want to do?Why do you want to merge those two object into one?

How to add global where clause for all find methods of Spring data JPA with Hibernate?

We are working on web application using Spring data JPA with hibernate.
In the application there is a field of compid in each entity.
Which means in every DB call (Spring Data methods) will have to be checked with the compid.
I need a way that, this "where compid = ?" check to be injected automatically for every find method.
So that we won't have to specifically bother about compid checks.
Is this possible to achieve from Spring Data JPA framework?
Maybe Hibernate‘s annotation #Where will help you. It adds the passed condition to any JPA queries related to the entity. For example
#Entity
#Where(clause = "isDeleted='false'")
public class Customer {
//...
#Column
private Boolean isDeleted;
}
More info: 1, 2
Agree with Abhijit Sarkar.
You can achieve your goal hibernate listeners and aspects. I can suggest the following :
create an annotation #Compable (or whatever you call it) to mark service methods
create CompAspect which should be a bean and #Aspect. It should have something like this
#Around("#annotation(compable)")`
public Object enableClientFilter(ProceedingJoinPoint pjp, Compable compable) throws Throwable {
Session session = (Session) em.getDelegate();
try {
if (session.isOpen()) {
session.enableFilter("compid_filter_name")
.setParameter("comp_id", your_comp_id);
}
return pjp.proceed();
} finally {
if (session.isOpen()) {
session.disableFilter("filter_name");
}
}
}
em - EntityManager
3)Also you need to provide hibernate filters. If you use annotation this can look like this:
#FilterDef(name="compid_filter_name", parameters=#ParamDef(name="comp_id", type="java.util.Long"))
#Filters(#Filter(name="compid_filter_name", condition="comp_id=:comp_id"))
So your condition where compid = ? will be #Service method below
#Compable
someServicweMethod(){
List<YourEntity> l = someRepository.findAllWithNamesLike("test");
}
That's basically it for Selects,
For updates/deletes this scheme requires an EntityListener.
Like other people have said there is no set method for this
One option is to look at Query by example - from the spring data documentation -
Person person = new Person();
person.setFirstname("Dave");
Example<Person> example = Example.of(person);
So you could default compid in the object, or parent JPA object
Another option is a custom repository
I can contribute a 50% solution. 50% because it seems to be not easy to wrap Query Methods. Also custom JPA queries are an issue for this global approach. If the standard finders are sufficient it is possible to extend an own SimpleJpaRepository:
public class CustomJpaRepositoryIml<T, ID extends Serializable> extends
SimpleJpaRepository<T, ID> {
private JpaEntityInformation<T, ?> entityInformation;
#Autowired
public CustomJpaRepositoryIml(JpaEntityInformation<T, ?> entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityInformation = entityInformation;
}
private Sort applyDefaultOrder(Sort sort) {
if (sort == null) {
return null;
}
if (sort.isUnsorted()) {
return Sort.by("insert whatever is a default").ascending();
}
return sort;
}
private Pageable applyDefaultOrder(Pageable pageable) {
if (pageable.getSort().isUnsorted()) {
Sort defaultSort = Sort.by("insert whatever is a default").ascending();
pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), defaultSort);
}
return pageable;
}
#Override
public Optional<T> findById(ID id) {
Specification<T> filterSpec = filterOperatorUserAccess();
if (filterSpec == null) {
return super.findById(id);
}
return findOne(filterSpec.and((Specification<T>) (root, query, criteriaBuilder) -> {
Path<?> path = root.get(entityInformation.getIdAttribute());
return criteriaBuilder.equal(path, id);
}));
}
#Override
protected <S extends T> TypedQuery<S> getQuery(Specification<S> spec, Class<S> domainClass, Sort sort) {
sort = applyDefaultOrder(sort);
Specification<T> filterSpec = filterOperatorUserAccess();
if (filterSpec != null) {
spec = (Specification<S>) filterSpec.and((Specification<T>) spec);
}
return super.getQuery(spec, domainClass, sort);
}
}
This implementation is picked up e.g. by adding it to the Spring Boot:
#SpringBootApplication
#EnableJpaRepositories(repositoryBaseClass = CustomJpaRepositoryIml.class)
public class ServerStart {
...
If you need this kind of filtering also for Querydsl it is also possible to implement and register a QuerydslPredicateExecutor.

Is it necessary to create an repository and a service for each entity? [duplicate]

This question already has an answer here:
Are you supposed to have one repository per table in JPA?
(1 answer)
Closed 4 years ago.
I'm using Hibernate+Spring and a database to persist my entities. I'm already using JpaRepository to create my repositories but even then, it seems I must create one interface extending JpaRepository for each entity. Worst, I'm creating one service to each entity. All of them very similar.
There's any way to create a generic service and a generic repository? Is it really necessary to implement each one of them?
By the moment, I have repositories like this:
#Repository
public interface PhaseRepository extends JpaRepository<Phase, Serializable> {
}
and services like this:
#Service
public class PhaseService {
#Autowired
PhaseRepository repository;
#Transactional
public Phase create(Phase entity) {
return repository.save(entity);
}
#Transactional(rollbackFor = EntityNotFound.class)
public Phase delete(int id) throws EntityNotFound {
Phase deleted = repository.findOne(id);
if (deleted == null) {
throw new EntityNotFound();
}
repository.delete(deleted);
return deleted;
}
#Transactional(rollbackFor = EntityNotFound.class)
public Phase update(Phase entity) throws EntityNotFound {
Phase updated = repository.findOne(entity.getId());
if (updated == null) {
throw new EntityNotFound();
}
repository.saveAndFlush(entity);
return updated;
}
public Phase findById(int id) throws EntityNotFound {
Phase entity = repository.findOne(id);
if (entity == null) {
throw new EntityNotFound();
}
return entity;
}
}
I'm using 12 entities and everyone has the same service methods.
Thanks!
Probably you'll need the 12 repositories. But maybe you won't need 12 services. A service could handle the access to several repositories. It depends on your logic and how "important" are every entity.
For example, if you had the entities User and Address you could have UserRepository and AddressRepository. But only UserService, with methods like addAddress(User user, Address address)...
All in all, I'd recomend you to organize your services accordingly your business logic instead of a bunch of CRUDs
You need 12 repositories, because Spring initializes instances of your declared interfaces. Spring already did a good job, I don't see a problem here. Imagine a situation back the days, when you had to implement these repositories by boilerplate JDBC code.
But you don't automatically need services for your repositories. Create services for your application purposes and autowire necessary repositories inside them. But I have created an example code, if you want generic service without copy-pasting code:
public abstract class AbstractService<T extends AbstractEntity, K extends Serializable> {
protected JpaRepository<T, K> repository;
public AbstractService(final JpaRepository<T, K> repository) {
this.repository = repository;
}
#Transactional(rollbackFor = EntityNotFound.class)
public T delete(final K id) throws EntityNotFound {
final T deleted = this.repository.findOne(id);
if (deleted == null) {
throw new EntityNotFound();
}
this.repository.delete(deleted);
return deleted;
}
...
}
Implementing class would be:
#Service
public class PhaseService extends AbstractService<PhaseEntity, Integer> {
#Autowired
public PhaseService (final PhaseRepository repository) {
super(repository);
}
}
Also a few tips:
You don't need EntityNotFound exception. Just check if entity is null.
Instead of Serializable use primary key strong type - an Integer for example.

Using generics in Spring Data JPA repositories

I have a number of simple object types that need to be persisted to a database. I am using Spring JPA to manage this persistence. For each object type I need to build the following:
import org.springframework.data.jpa.repository.JpaRepository;
public interface FacilityRepository extends JpaRepository<Facility, Long> {
}
public interface FacilityService {
public Facility create(Facility facility);
}
#Service
public class FacilityServiceImpl implements FacilityService {
#Resource
private FacilityRepository countryRepository;
#Transactional
public Facility create(Facility facility) {
Facility created = facility;
return facilityRepository.save(created);
}
}
It occurred to me that it may be possible to replace the multiple classes for each object type with three generics based classes, thus saving a lot of boilerplate coding. I am not exactly sure how to go about it and in fact if it is a good idea?
First of all, I know we're raising the bar here quite a bit but this is already tremendously less code than you had to write without the help of Spring Data JPA.
Second, I think you don't need the service class in the first place, if all you do is forward a call to the repository. We recommend using services in front of the repositories if you have business logic that needs orchestration of different repositories within a transaction or has other business logic to encapsulate.
Generally speaking, you can of course do something like this:
interface ProductRepository<T extends Product> extends CrudRepository<T, Long> {
#Query("select p from #{#entityName} p where ?1 member of p.categories")
Iterable<T> findByCategory(String category);
Iterable<T> findByName(String name);
}
This will allow you to use the repository on the client side like this:
class MyClient {
#Autowired
public MyClient(ProductRepository<Car> carRepository,
ProductRepository<Wine> wineRepository) { … }
}
and it will work as expected. However there are a few things to notice:
This only works if the domain classes use single table inheritance. The only information about the domain class we can get at bootstrap time is that it will be Product objects. So for methods like findAll() and even findByName(…) the relevant queries will start with select p from Product p where…. This is due to the fact that the reflection lookup will never ever be able to produce Wine or Car unless you create a dedicated repository interface for it to capture the concrete type information.
Generally speaking, we recommend creating repository interfaces per aggregate root. This means you don't have a repo for every domain class per se. Even more important, a 1:1 abstraction of a service over a repository is completely missing the point as well. If you build services, you don't build one for every repository (a monkey could do that, and we're no monkeys, are we? ;). A service is exposing a higher level API, is much more use-case drive and usually orchestrates calls to multiple repositories.
Also, if you build services on top of repositories, you usually want to enforce the clients to use the service instead of the repository (a classical example here is that a service for user management also triggers password generation and encryption, so that by no means it would be a good idea to let developers use the repository directly as they'd effectively work around the encryption). So you usually want to be selective about who can persist which domain objects to not create dependencies all over the place.
Summary
Yes, you can build generic repositories and use them with multiple domain types but there are quite strict technical limitations. Still, from an architectural point of view, the scenario you describe above shouldn't even pop up as this means you're facing a design smell anyway.
This is very possible! I am probably very late to the party. But this will certainly help someone in the future. Here is a complete solution that works like a charm!
Create BaseEntity class for your entities as follows:
#MappedSuperclass
public class AbstractBaseEntity implements Serializable{
#Id #GeneratedValue
private Long id;
#Version
private int version;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public AbstractBaseEntity() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
// getters and setters
}
Create a generic JPA Repository interface for your DAO persistence as follows:
NB. Remember to put the #NoRepositoryBean so that JPA will not try to find an implementation for the repository!
#NoRepositoryBean
public interface AbstractBaseRepository<T extends AbstractBaseEntity, ID extends Serializable>
extends JpaRepository<T, ID>{
}
Create a Base Service class that uses the above base JPA repository. This is the one that other service interfaces in your domain will simply extend as follows:
public interface AbstractBaseService<T extends AbstractBaseEntity, ID extends Serializable>{
public abstract T save(T entity);
public abstract List<T> findAll(); // you might want a generic Collection if u prefer
public abstract Optional<T> findById(ID entityId);
public abstract T update(T entity);
public abstract T updateById(T entity, ID entityId);
public abstract void delete(T entity);
public abstract void deleteById(ID entityId);
// other methods u might need to be generic
}
Then create an abstract implementation for the base JPA repository & the basic CRUD methods will also be provided their implementations as in the following:
#Service
#Transactional
public abstract class AbstractBaseRepositoryImpl<T extends AbstractBaseEntity, ID extends Serializable>
implements AbstractBaseService<T, ID>{
private AbstractBaseRepository<T, ID> abstractBaseRepository;
#Autowired
public AbstractBaseRepositoryImpl(AbstractBaseRepository<T, ID> abstractBaseRepository) {
this.abstractBaseRepository = abstractBaseRepository;
}
#Override
public T save(T entity) {
return (T) abstractBaseRepository.save(entity);
}
#Override
public List<T> findAll() {
return abstractBaseRepository.findAll();
}
#Override
public Optional<T> findById(ID entityId) {
return abstractBaseRepository.findById(entityId);
}
#Override
public T update(T entity) {
return (T) abstractBaseRepository.save(entity);
}
#Override
public T updateById(T entity, ID entityId) {
Optional<T> optional = abstractBaseRepository.findById(entityId);
if(optional.isPresent()){
return (T) abstractBaseRepository.save(entity);
}else{
return null;
}
}
#Override
public void delete(T entity) {
abstractBaseRepository.delete(entity);
}
#Override
public void deleteById(ID entityId) {
abstractBaseRepository.deleteById(entityId);
}
}
How to use the above abstract entity, service, repository, and implementation:
Example here will be a MyDomain entity. Create a domain entity that extends the AbstractBaseEntity as follows:
NB. ID, createdAt, updatedAt, version, etc will be automatically be included in the MyDomain entity from the AbstractBaseEntity
#Entity
public class MyDomain extends AbstractBaseEntity{
private String attribute1;
private String attribute2;
// getters and setters
}
Then create a repository for the MyDomain entity that extends the AbstractBaseRepository as follows:
#Repository
public interface MyDomainRepository extends AbstractBaseRepository<MyDomain, Long>{
}
Also, Create a service interface for the MyDomain entity as follows:
public interface MyDomainService extends AbstractBaseService<MyDomain, Long>{
}
Then provide an implementation for the MyDomain entity that extends the AbstractBaseRepositoryImpl implementation as follows:
#Service
#Transactional
public class MyDomainServiceImpl extends AbstractBaseRepositoryImpl<MyDomain, Long>
implements MyDomainService{
private MyDomainRepository myDomainRepository;
public MyDomainServiceImpl(MyDomainRepository myDomainRepository) {
super(myDomainRepository);
}
// other specialized methods from the MyDomainService interface
}
Now use your `MyDomainService` service in your controller as follows:
#RestController // or #Controller
#CrossOrigin
#RequestMapping(value = "/")
public class MyDomainController {
private final MyDomainService myDomainService;
#Autowired
public MyDomainController(MyDomainService myDomainService) {
this.myDomainService = myDomainService;
}
#GetMapping
public List<MyDomain> getMyDomains(){
return myDomainService.findAll();
}
// other controller methods
}
NB. Make sure that the AbstractBaseRepository is annotated with #NoRepositoryBean so that JPA does not try to find an implementation for the bean.
Also the AbstractBaseServiceImpl must be marked abstract, otherwise JPA will try to autowire all the children daos of the AbstractBaseRepository in the constructor of the class leading to a NoUniqueBeanDefinitionException since more than 1 daos (repository) will be injected when the bean is created!
Now your service, repository, and implementations are more reusable. We all hate boilerplate!
Hope this helps someone.
I am working a project to create the generic repository for cassandra with spring data.
Firstly create a repository interface with code.
StringBuilder sourceCode = new StringBuilder();
sourceCode.append("import org.springframework.boot.autoconfigure.security.SecurityProperties.User;\n");
sourceCode.append("import org.springframework.data.cassandra.repository.AllowFiltering;\n");
sourceCode.append("import org.springframework.data.cassandra.repository.Query;\n");
sourceCode.append("import org.springframework.data.repository.CrudRepository;\n");
sourceCode.append("\n");
sourceCode.append("public interface TestRepository extends CrudRepository<Entity, Long> {\n");
sourceCode.append("}");
Compile the code and get the class, I use org.mdkt.compiler.InMemoryJavaCompiler
ClassLoader classLoader = org.springframework.util.ClassUtils.getDefaultClassLoader();
compiler = InMemoryJavaCompiler.newInstance();
compiler.useParentClassLoader(classLoader);
Class<?> testRepository = compiler.compile("TestRepository", sourceCode.toString());
And initialize the repository in spring data runtime. This is a little tricky as I debug the SpringData code to find how it initialize a repository interface in spring.
CassandraSessionFactoryBean bean = context.getBean(CassandraSessionFactoryBean.class);
RepositoryFragments repositoryFragmentsToUse = (RepositoryFragments) Optional.empty().orElseGet(RepositoryFragments::empty);
CassandraRepositoryFactory factory = new CassandraRepositoryFactory(
new CassandraAdminTemplate(bean.getObject(), bean.getConverter()));
factory.setBeanClassLoader(compiler.getClassloader());
Object repository = factory.getRepository(testRepository, repositoryFragmentsToUse);
Now you can try the save method of the repository and you can try other methods such as findById.
Method method = repository.getClass().getMethod("save", paramTypes);
T obj = (T) method.invoke(repository, params.toArray());
A full sample code and implementation I have put in this repo
https://github.com/maye-msft/generic-repository-springdata.
You can extend it to JPA with the similar logic.

Categories