I've been working on converting our system from manual hibernate to spring using JPA.
So far it has been going great. At some point I came across the need to query for all the instances of a class that implement another class.
Lets look at the following design:
Base Class: Machine
#Entity
#Table(name = "MACHINE")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
#DiscriminatorValue(value = "Machine")
public abstract class Machine implements Resource {
// ...
}
Interface: Monitorable
public interface Monitorable {
// ...
}
Subclass: Linux
#Entity
#DiscriminatorValue("Linux")
public class Linux extends Machine implements Monitorable {
// ...
}
Using Hibernate it looked like this:
public static List<Machine> GetALL(Class<?> T) {
// .. hibernate session stuff ..
result = session.createCriteria(T).setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY).list();
// .. closing session and error handling.
}
I've got a working Machine repository and I can easily use findAll() and then filter, but it seems like a waste.
Whilst searching for an answer I found the following implementation of a repository which lets us retrieve all sub-classes.
#NoRepositoryBean
public interface BaseMachineRepo<EntityType extends Machine> extends CrudRepository<EntityType, Long> {
#Query("select e from #{#entityName} e")
List<EntityType> findAllByType();
}
However, I don't quite see how to change this implementation to work for an interface instead of a class.
The closest I got was changing the signature:
public interface BaseMachineRepo<EntityType extends Machine & Monitorable> extends CrudRepository<EntityType, Long>
However this will not solve the issue since the type is not 'Monitorbale' but 'Linux'. It does not change the query, just enforces more restrictions on the caller.
Thanks.
Edit:
I am aware that an interface doesn't affect the table itself, otherwise I would've known how to query for it. On the other hand, writing a custom repo which would check if the type (for example Linux) is an instanceof the interface feels really wrong.
This is the working solution that I ended up writing:
public class MachineRepoImpl implements MachineRepoCustom {
#Autowired
private MachineRepo machineRepo;
#Autowired
private ApplicationContext applicationContext;
#Override
public List<Machine> findMonitorables() {
List<Machine> machines = machineRepo.findAll();
Map<String, Monitorable> beansOfType = applicationContext.getBeansOfType(Monitorable.class);
Collection<Monitorable> monitorables = beansOfType.values();
Set<Class<? extends Monitorable>> set = new HashSet<>();
List<Machine> result = new ArrayList<Machine>();
// parses monitorable classes
for(Monitorable m : monitorables)
{
set.add(m.getClass());
}
// checks whether the entry in the table is monitorable
for (Machine machine : machines) {
if (set.contains(machine.getClass())) {
result.add(machine);
}
}
return result;
}
Related
Hello I have the following example:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "EVENT_TYPE")
abstract class BaseEvent {
}
#Entity
#DiscriminatorValue("ACCEPT_EVENT")
class AcceptEvent {
}
#Entity
#DiscriminatorValue("CANCEL_EVENT")
class CancelEvent {
}
I would like to write a repository for this Polymorphic entity. The thing is that I may have many EventTypes so I would prefer If I don't have to write One Repository class per Event Type. I was hoping that #{#entityName} can be applied in a generic repository like this:
interface EventRepositort<T> extends CRUDRepository<T> {
#Query(select e from #{#entityName} e where e.completionDate is null)
public T findIncompleteEvents();
}
I was hoping that if I have a service and I inject the repository with specific generic type it will figure out what the #{#entityName} so I did the following:
#Service
class EventService {
#Autowire
private EventRepository<AcceptEvent> acceptEventRepo;
public runUnfinishedEvents() {
List<AcceptEvent> unfinishedEvents = acceptEventRepo.findIncompleteEvents();
}
}
It apears though that even though I have defined what the generic type is on the event repository the method findIncompleteEvents is not able to resolve the generic declaration with #{#entityName} basicly it returns both the CancelEvent and the AcceptEvent
Now my question:
Is there a way to express what I want without writing 10 different reposities for each subclass of the BaseEvent ?
I´ve got several Java documents that are stored in the same collection "app" in mongodb. My intention is use it for the common persistence actions (insert, find, delete...). The problem comes when I try to define repositories for only one of the classes as the repository that I´ve defined will search all the entities, and what I need is a repository where I can call to all the mongorepository standard functions (find, findAll, findby...). My explanation can be hard to understand, this is what I have now:
Base document:
#Document(collection = "app")
#NoRepositoryBean
public abstract class AbstractApplicationDocument {
#Id
public String mongoId;
// more variables, getters, setters...
}
One of the subdocuments (there will be many).
#EqualsAndHashCode(callSuper=true)
public class ClientApplicationDocument extends AbstractApplicationDocument
{
#Id
public String mongoId;
}
Base Repository
#NoRepositoryBean
public interface ApplicationRepository<T, ID extends Serializable> extends MongoRepository<T, ID> {
}
Extended repository (where I want to filter by class)
public interface HttpClientRepository extends ApplicationRepository<HttpClientDocument, String> {
}
Sample of temporary solution that I'd like to avoid if I can reuse the MongoDb utilities
#Component
public class HttpClientRepositoryImpl {
#Autowired
private HttpClientRepository clientRepo;
#Autowired
private MongoTemplate mongo;
public List<HttpClientDocument> findAll() {
Query query = new Query();
query.addCriteria(Criteria.where("_class").is(HttpClientDocument.class.getName()));
mongo.getConverter();
return mongo.find(query, HttpClientDocument.class,"app");
}
}
Relevant information in gradle. I add this because I've seen some solutions that are not valid for me. Probably because they are using different libraries:
compile("org.springframework.boot:spring-boot-starter-data-mongodb:${springBootVersion}")
compile ("org.springframework:spring-context-support:4.1.8.RELEASE");
Is there any simple solution?
You can customize Spring Data repositories in many ways. You can provide custom repository base classes or provide custom implementation classes.
For your question in particular, SimpleMongoRepository does not restrict query on the _class field. You can see below an example for a custom repository base class that restricts the findAll query method to use only the declared entity type.
You would be required to apply that pattern also for other methods which are declared on the MongoRepository level that you want to restrict to the particular class type.
#EnableMongoRepositories(repositoryBaseClass = MyMongoRepository.class)
static class Config {
}
static class MyMongoRepository<T, ID extends Serializable> extends SimpleMongoRepository<T, ID> {
private final MongoEntityInformation<T, ID> metadata;
private final MongoOperations mongoOperations;
public MyMongoRepository(MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations) {
super(metadata, mongoOperations);
this.metadata = metadata;
this.mongoOperations = mongoOperations;
}
#Override
public List<T> findAll() {
Query query = new Query();
query.restrict(this.metadata.getJavaType());
return this.mongoOperations.find(query, this.metadata.getJavaType(), this.metadata.getCollectionName());
}
}
findAll queries restrict now the class type and queries for a e.g. ModelRepository extends MongoRepository<Model, String> would look like:
{ "_class" : { "$in" : [ "com.example.Model"]}}
Query methods with #Query can be used to pass in the class type (#Query("{'_class': ?0}")) but there's no way to contrain the class type for derived query methods (List<Model> findByFirstnameAndLastname(…)).
There's an open ticket in our Jira related to restricting types of the repository. That particular ticket is related to polymorphic queries but in its essence, it's related to type restrictions on query methods.
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 new to stack overflow and working on spring jpa data with hibernate and mysql. I have created One JpaRepository for each entity class. But now I feel that I should use One repository for all entities because In all my repositories has common CRUD operation methods.
save()
update()
delete()
findOne()
findAll()
Besides of above methods, I have other custom methods also in my applications.
my aim is to implement GenericRepo like,
public interface MyGenericRepo extends JpaRepository<GenericEntity,Integer>
{
}
my entities will be like:
class Place extends GenericEntity
{
private Event event;
}
class Event extends GenericEntity
{
}
class Offer extends GenericEntity
{
private Place place;
}
class User extends GenericEntity
{
private Place place;
}
when I call:
MyGenericRepo myRepo;
GenericEntity place=new Place();
myRepo.save(place);
It should save place.
[http://openjpa.apache.org/builds/1.0.2/apache-openjpa-1.0.2/docs/manual/jpa_overview_mapping_inher.html#jpa_overview_mapping_inher_joined][1]
I have referred above link and I found that Jpa Inheritance with Joined and Table-Per-Class strategies are similar to what I am looking for, but these all have certain limitations.So please tell me should I try to implement this generic thing.If I get any demo code then I will be very greatful...
Thanks..
How to make generic jpa repository? Should I do this? Why?
If you want to create your own Repos (and not spring data which does some work for you) your example isn't bad, i am using a similar strategy in one application.
Here a few thoughts to improve the generic way:
I've added the ID-information in my basic domain which is implemented by all domain objects:
public interface UniqueIdentifyable<T extends Number> {
T getId();
void setId(T id);
}
In the next step i've created a generic CRUDRepo:
public interface CRUDRepository<ID extends Number, T extends UniqueIdentifyable<ID>>{
ID insert(T entity);
void delete(T entity);
....
}
And I am using an abstract class for the CRUDRepo:
public abstract class AbstractCRUDRepo<ID extends Number, T extends UniqueIdentifyable<ID>> implements CRUDRepo<ID, T>, {...}
a domain repo api will now look like:
public interface UserRepo extends CRUDRepo<Integer, User > {
User mySpecificQuery(..);
}
and finally you can implement your repo via:
public class UserRepoImpl extends AbstractCRUDRepo<Integer, User > implements UserRepo {
public User mySpecificQuery(..){..}
}
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.