I am having trouble with generic abstract classes and multiple parameters. In brief, I have a database table with a composite key defined by ProductAttributeEntity.class and ProductAttributeEntityPk.class, where ProductAttributeEntityPk.class is the composite key. I am struggling to pass ProductAttributeEntityPk.class into the abstract class for the getById method, which gives me, “Class must either be declared abstract or implement abstract method” error.
How can I get around this problem?
PS: I am new to java
Abstract DAO class (https://pastebin.com/n2FBQCg2)
package com.beetlehand.model.dao;
import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import java.util.List;
import java.util.Map;
public abstract class AbstractDao<T, S> {
protected EntityManager entityManager;
public EntityManager getEntityManager() {
if(this.entityManager == null) {
this.entityManager = Persistence
.createEntityManagerFactory("NewPersistenceUnit")
.createEntityManager();
}
return this.entityManager;
}
public abstract T getById(S id);
public abstract T getOneBy(Map<String, Object> params);
public abstract List<T> getBy(String predicates, Map<String, Object> params);
public abstract List<T> getBy(String predicates, Map<String, Object> params, String orderBy);
public abstract List<T> getBy(String predicates, Map<String, Object> params, String orderBy
, Integer limit);
public abstract List<T> getBy(String predicates, Map<String, Object> params, String orderBy
, Integer limit, Integer offset);
public abstract Long count();
public abstract Long countBy(String predicates, Map<String, Object> params);
}
DAO class (https://pastebin.com/tkMYLVZE)
package com.beetlehand.model.dao;
import com.beetlehand.model.ProductAttributeEntity;
import com.beetlehand.model.ProductAttributeEntityPk;
import org.apache.commons.lang3.StringUtils;
import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ProductAttributeDao extends AbstractDao<ProductAttributeEntity, ProductAttributeEntityPk> {
public ProductAttributeEntity getById(ProductAttributeEntityPk id) {
if (id == null) return null;
return getEntityManager().find(ProductAttributeEntity.class, id);
}
/** other methods implementation **/
}
Entity classes (https://pastebin.com/5YySBt7X, https://pastebin.com/8rwD4kec)
package com.beetlehand.model;
import javax.persistence.*;
#Entity
#Table(name = "product_attribute", schema = "beetlehand", catalog = "")
#IdClass(ProductAttributeEntityPK.class)
public class ProductAttributeEntity {
private int productId;
private int attributeValueId;
/** getter, setters, equals and hashcode implementation **/
}
package com.beetlehand.model;
import javax.persistence.Column;
import javax.persistence.Id;
import java.io.Serializable;
public class ProductAttributeEntityPK implements Serializable {
private int productId;
private int attributeValueId;
/** getter, setters, equals and hashcode implementation **/
}
Related
I'm developing an application using Vaadin and I am having some trouble:
When I try to save and object with Hibernate, nothing appears in my table unless i double save it.
Code from my class:
if (referenciacao==null){
referenciacao = (Referenciacao)referenciacaoSession.newEntity();
}
referenciacao.setMedico((Medico)cbbMedico.getValue());
referenciacao.setOrigem((Origem)cbbOrigem.getValue());
referenciacao.setReferenciacaotipo((ReferenciacaoTipo)cbbReferenciacaoTipo.getValue());
referenciacao=referenciacaoSession.saveAndFlush(referenciacao);
My Referenciacao Entity:
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
/**
*
* #author pepatusco
*/
#SuppressWarnings("serial")
#Entity
#Table(name = "referenciacao")
public class Referenciacao extends BaseBean implements Serializable {
public static final String PROPERTY_ID = "id";
public static final String PROPERTY_MEDICO = "medico";
public static final String PROPERTY_ORIGEM = "origem";
public static final String PROPERTY_REFTIPO = "referenciacaoTipo";
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "medico_id")
private Medico medico;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "origem_id")
private Origem origem;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "referenciacao_tipo_id")
private ReferenciacaoTipo referenciacaoTipo;
public Medico getMedico() {
return medico;
}
public void setMedico(Medico medico) {
this.medico = medico;
}
public Origem getOrigem() {
return origem;
}
public void setOrigem(Origem origem) {
this.origem = origem;
}
public ReferenciacaoTipo getReferenciacaotipo() {
return referenciacaoTipo;
}
public void setReferenciacaotipo(ReferenciacaoTipo referenciacaoTipo) {
this.referenciacaoTipo = referenciacaoTipo;
}
public Referenciacao() {
//this.id = null;
}
}
My Referenciacao DAO:
import java.util.List;
import javax.transaction.Transactional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import pt.app.dpn.base.common.beans.entity.Medico;
import pt.app.dpn.base.common.beans.entity.Origem;
import pt.app.dpn.base.common.beans.entity.Referenciacao;
import pt.app.dpn.base.common.beans.entity.ReferenciacaoTipo;
/**
*
* #author pepatusco
*/
#Repository
#Transactional
public interface ReferenciacaoRepository extends JpaRepository<Referenciacao, Long> {
List<Referenciacao> findByMedico(Medico medico);
List<Referenciacao> findByOrigem(Origem origem);
List<Referenciacao> findByReferenciacaoTipo(ReferenciacaoTipo referenciacaoTipo);
Referenciacao findByMedicoAndOrigemAndReferenciacaoTipo(Medico medico, Origem origem, ReferenciacaoTipo referenciacaoTipo);
Referenciacao findById(Long id);
}
My ReferenciacaoSession:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.enterprise.inject.Default;
import javax.inject.Inject;
import javax.transaction.Transactional;
import pt.app.dpn.base.common.beans.dao.ReferenciacaoRepository;
import pt.app.dpn.base.common.beans.entity.BaseBean;
import pt.app.dpn.base.common.beans.entity.Medico;
import pt.app.dpn.base.common.beans.entity.Origem;
import pt.app.dpn.base.common.beans.entity.Referenciacao;
import pt.app.dpn.base.common.beans.entity.ReferenciacaoTipo;
/**
*
* #author pepatusco
*
*/
#Default
#Stateless
#Local(ReferenciacaoSessionLocal.class)
public class ReferenciacaoSession implements ReferenciacaoSessionLocal, Serializable {
#Inject
private ReferenciacaoRepository referenciacaoRepository;
#Override
public void flush() {
referenciacaoRepository.flush();
}
#Transactional
#Override
public <S extends Referenciacao> S saveAndFlush(S s) {
return referenciacaoRepository.saveAndFlush(s);
}
#Override
public Long getCount() {
return referenciacaoRepository.count();
}
#Override
public List<BaseBean> findAll() {
List<BaseBean> listBaseBean = new ArrayList<>();
referenciacaoRepository.findAll().forEach((referenciacao) -> {
listBaseBean.add(referenciacao);
});
return listBaseBean;
}
#Transactional
#Override
public void save(Object o) {
referenciacaoRepository.save((Referenciacao)o);
referenciacaoRepository.flush();
}
#Override
public void remove(Object o) {
referenciacaoRepository.delete((Referenciacao)o);
}
#Override
public Object newEntity() {
return new Referenciacao();
}
#Override
public List<Referenciacao> findByMedico(Medico medico) {
return referenciacaoRepository.findByMedico(medico);
}
#Override
public List<Referenciacao> findByOrigem(Origem origem) {
return referenciacaoRepository.findByOrigem(origem);
}
#Override
public Referenciacao findById(Long id) {
return referenciacaoRepository.findById(id);
}
#Override
public List<Referenciacao> findByReferenciacaoTipo(ReferenciacaoTipo referenciacaoTipo) {
return referenciacaoRepository.findByReferenciacaoTipo(referenciacaoTipo);
}
#Override
public Referenciacao findByMedicoAndOrigemAndReferenciacaoTipo(Medico medico, Origem origem, ReferenciacaoTipo referenciacaoTipo) {
return referenciacaoRepository.findByMedicoAndOrigemAndReferenciacaoTipo(medico, origem, referenciacaoTipo);
}
}
Can anyone tell me why, when I do save, nothing appears in the table but when I do save again it saves a Referenciacao in the table?
I can't see the reason for this behavior, so let me at least try to help to debug this thing.
I see multiple possible things that might be going on:
The repository for your entity doesn't get called on the first try, for some reason outside of the code you showed us.
The repository does not store your entity.
Something goes haywire with your transactions, causing you not to see the changes (I'm actually betting on this one).
In order to rule out (1) put a breakpoint or a logging statement next to the referenciacaoRepository.save and referenciacaoRepository.saveAndFlush statements to ensure, that code path gets executed.
In order to rule out (2) activate SQL logging, to see if SQL statements actually get executed as expected. Obviously, you should see insert statements getting executed.
To tackle the third one, activate transaction logging to see when transactions start and end. There should be a transaction getting started before the save and getting commited afterward. Verify that this actually happens and that there are no other transactions running.
Let us know if you were able to solve your problem.
Side note: Why do you have ReferenciacaoSession? It really doesn't do anything and seems to be perfectly replaceable by the repository.
I have a base Repository, say IBaseRepository which is
public interface IBaseRepository<T extends BaseEntity<PK>, PK extends Serializable>
extends JpaRepository<T, PK>, JpaSpecificationExecutor<T> {
}
now every repository class, for example UserRepository extends from this base repository. How can I add a general method like
T findOne(String filter, Map<String, Object> params);
for all inherited classes so that calling
Map<String,Object> params = new HashMap<String,Object>();
params.put("username","Lord");
params.put("locked",Status.LOCKED);
userRepo.findeOne("username = :username AND status = :locked",params);
return me a single record with dynamic where clause.
You can do the Following
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.NoRepositoryBean;
import java.io.Serializable;
import java.util.Map;
/**
* Created by shazi on 1/11/2017.
*/
#NoRepositoryBean
public interface IBaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
T findOne(String filter, Map<String, Object> params);
}
And Implement it as follows.
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.io.Serializable;
import java.util.Map;
/**
* Created by shazi on 1/11/2017.
*/
public class BaseRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements IBaseRepository<T, ID> {
private final EntityManager entityManager;
private final JpaEntityInformation entityInformation;
public BaseRepositoryImpl(JpaEntityInformation entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
// Keep the EntityManager around to used from the newly introduced methods.
this.entityManager = entityManager;
this.entityInformation = entityInformation;
}
#Override
public T findOne(String filter, Map<String, Object> params) {
final String jpql = "FROM " + entityInformation.getEntityName() + " WHERE " + filter;
Query query = entityManager.createQuery(jpql);
for (Map.Entry<String, Object> value:params.entrySet()) {
query.setParameter(value.getKey(), value.getValue());
}
return (T) query.getSingleResult();
}
}
And configure it as follows
#Configuration
#EnableJpaRepositories(repositoryBaseClass = BaseRepositoryImpl.class)
#EnableTransactionManagement
public class RepoConfig {
or in XML
<repositories base-class="….BaseRepositoryImpl" />
Finally you can use it as follows;
User found = userRepository.findOne("name = :name", Collections.singletonMap("name", "name"));
But you have to make sure that your query WHERE is such that the Query will always return 1 result only. See this post
I need to implement soft delete functionality(Maintain a boolean field in table and filter all query based on this).
Below link has solution for hibernate only.
Handling soft-deletes with Spring JPA
Since my application is very old, I don't want to change each existing query. I am looking for solution like one place change in spring data classes.
Spring mongo data version: 1.5.0.RELEASE
Add Boolean Field active to every class which is mapped with Collection
set the same true for all valid Documents and false for non valid documnets
private Boolean active = Boolean.TRUE;
and can chnage your Query to
Long countByActiveTrueAndAccountStatusNot(AccountStatus status);
First Step. Override default methods like as findAll(), findById(), exists().... For this you should override mongoTemplate, it simple).
Add to your entities field "deletedAt":
#Document("costAreas")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#FieldDefaults(level = AccessLevel.PRIVATE)
#Builder
public class User{
#Id
String id;
String name;
LocalDateTime deletedAt;
}
PS: Filed "deletedAt" contains the date of deletion (if this field is null then document wasn't deleted).
Create CustomMongoTemplate:
import com.mongodb.client.MongoClient;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;
public class CustomMongoTemplate extends MongoTemplate {
public CustomMongoTemplate(MongoTemplate mongoTemplate) {
super(mongoTemplate.getMongoDatabaseFactory());
}
public CustomMongoTemplate(MongoClient mongoClient, String databaseName) {
super(mongoClient, databaseName);
}
public CustomMongoTemplate(MongoDatabaseFactory mongoDbFactory) {
super(mongoDbFactory);
}
public CustomMongoTemplate(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter) {
super(mongoDbFactory, mongoConverter);
}
#Override
public <T> List<T> find(Query query, Class<T> entityClass, String collectionName) {
Assert.notNull(query, "Query must not be null!");
Assert.notNull(collectionName, "CollectionName must not be null!");
Assert.notNull(entityClass, "EntityClass must not be null!");
query.addCriteria(Criteria.where("deletedAt").exists(Boolean.FALSE));
return super.find(query, entityClass, collectionName);
}
#Nullable
#Override
public <T> T findById(Object id, Class<T> entityClass, String collectionName) {
T t = super.findById(id, entityClass, collectionName);
try {
Field field = entityClass.getDeclaredField("deletedAt");
field.setAccessible(Boolean.TRUE);
if (Objects.nonNull(field.get(t))) {
return null;
}
} catch (NoSuchFieldException | IllegalAccessException ignored) {
}
return t;
}
#Nullable
#Override
public <T> T findOne(Query query, Class<T> entityClass, String collectionName) {
Assert.notNull(query, "Query must not be null!");
Assert.notNull(entityClass, "EntityClass must not be null!");
Assert.notNull(collectionName, "CollectionName must not be null!");
query.addCriteria(Criteria.where("deletedAt").exists(Boolean.FALSE));
return super.findOne(query, entityClass, collectionName);
}
#Override
#SuppressWarnings("ConstantConditions")
public boolean exists(Query query, #Nullable Class<?> entityClass, String collectionName) {
if (query == null) {
throw new InvalidDataAccessApiUsageException("Query passed in to exist can't be null");
}
query.addCriteria(Criteria.where("deletedAt").exists(Boolean.FALSE));
return super.exists(query, entityClass, collectionName);
}
// You can also override ```delete()``` method, but I decided to not to do this
// Maybe here should add other methods: count, findAndModify and ect. It depends which methods you going to use.
}
Then create Bean in configuration class:
#Configuration
public class MyConfiguration {
//...
#Bean(name = "mongoTemplate")
CustomMongoTemplate customMongoTemplate(MongoDatabaseFactory databaseFactory, MappingMongoConverter converter) {
return new CustomMongoTemplate(databaseFactory, converter);
}
//...
}
And allow Spring to override default MongoTemplate bean. Add next thing to your application.properties file:
spring.main.allow-bean-definition-overriding=true
Replace delete() with set deletedAt:
// Deletion method
// Before
User = userRepository.findById(id);
userRepository.delete(user);
// Now
User = userRepository.findById(id);
user.setDeletedAt(LocalDateTime.now());
userRepository.save(user);
Second Step. Implement soft delete for method in Repositories (generated by JPA) like as findAllByEmail(String email), existsByNameAndUsername(String name, String username)....
Resource: https://blog.rpuch.com/2019/10/27/spring-data-mongo-soft-delete-repositories.html
SoftDeleteMongoQueryLookupStrategy
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor;
import org.springframework.data.mongodb.repository.query.PartTreeMongoQuery;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.lang.reflect.Method;
public class SoftDeleteMongoQueryLookupStrategy implements QueryLookupStrategy {
private final QueryLookupStrategy strategy;
private final MongoOperations mongoOperations;
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
public SoftDeleteMongoQueryLookupStrategy(QueryLookupStrategy strategy,
MongoOperations mongoOperations,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
this.strategy = strategy;
this.mongoOperations = mongoOperations;
this.evaluationContextProvider = evaluationContextProvider;
}
#Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
NamedQueries namedQueries) {
RepositoryQuery repositoryQuery = strategy.resolveQuery(method, metadata, factory, namedQueries);
// revert to the standard behavior if requested
if (method.getAnnotation(SeesSoftlyDeletedRecords.class) != null) {
return repositoryQuery;
}
if (!(repositoryQuery instanceof PartTreeMongoQuery)) {
return repositoryQuery;
}
PartTreeMongoQuery partTreeQuery = (PartTreeMongoQuery) repositoryQuery;
return new SoftDeletePartTreeMongoQuery(partTreeQuery);
}
private Criteria notDeleted() {
return new Criteria().andOperator(
Criteria.where("deletedAt").exists(false)
);
}
private class SoftDeletePartTreeMongoQuery extends PartTreeMongoQuery {
SoftDeletePartTreeMongoQuery(PartTreeMongoQuery partTreeQuery) {
super(partTreeQuery.getQueryMethod(), mongoOperations, new SpelExpressionParser(), evaluationContextProvider);
}
#Override
protected Query createQuery(ConvertingParameterAccessor accessor) {
Query query = super.createQuery(accessor);
return withNotDeleted(query);
}
#Override
protected Query createCountQuery(ConvertingParameterAccessor accessor) {
Query query = super.createCountQuery(accessor);
return withNotDeleted(query);
}
private Query withNotDeleted(Query query) {
return query.addCriteria(notDeleted());
}
}
}
SeesSoftlyDeletedRecords (if you marked method annotation then method will ignore soft-deletion)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface SeesSoftlyDeletedRecords {
}
SoftDeleteMongoRepositoryFactory
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import java.util.Optional;
public class SoftDeleteMongoRepositoryFactory extends MongoRepositoryFactory {
private final MongoOperations mongoOperations;
public SoftDeleteMongoRepositoryFactory(MongoOperations mongoOperations) {
super(mongoOperations);
this.mongoOperations = mongoOperations;
}
#Override
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(QueryLookupStrategy.Key key,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
Optional<QueryLookupStrategy> optStrategy = super.getQueryLookupStrategy(key,
evaluationContextProvider);
return Optional.of(createSoftDeleteQueryLookupStrategy(optStrategy.get(), evaluationContextProvider));
}
private SoftDeleteMongoQueryLookupStrategy createSoftDeleteQueryLookupStrategy(QueryLookupStrategy strategy,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
return new SoftDeleteMongoQueryLookupStrategy(strategy, mongoOperations, evaluationContextProvider);
}
}
SoftDeleteMongoRepositoryFactoryBean
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import java.io.Serializable;
public class SoftDeleteMongoRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable>
extends MongoRepositoryFactoryBean<T, S, ID> {
public SoftDeleteMongoRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
#Override
protected RepositoryFactorySupport getFactoryInstance(MongoOperations operations) {
return new SoftDeleteMongoRepositoryFactory(operations);
}
}
Add it to configuration
#Configuration
#EnableMongoRepositories(basePackages = {"path to package with your repositories"}, repositoryFactoryBeanClass = SoftDeleteMongoRepositoryFactoryBean.class)
public class MyConfiguration {
//...
}
Hope it will help someone)
How to create a validator restrictions for more fields through annotation in spring 4.* for example
#UniqueValidator
#Entity
#Table(name = "persons")
#UniqueValidator(message="Peson already exist",columns={"name","lastName"})
public class {
#Column
private String name;
#Column
private String lastName;
}
roughly speaking..
select ... from persons where name='qwerty' and lastName='asdfgh'
Here's one way to do it in Spring MVC and JSR 303 validation:
Create a constraint annotation:
package com.awgtek.model.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
/**
* The target field should be unique in the data store.
*/
#Documented
#Constraint(validatedBy = UniqueNameValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface UniqueName {
/**
* #return the error message template
*/
String message() default "{com.awgtek.model.validation.UniqueName.message}";
/**
* #return the groups the constraint belongs to
*/
Class<?>[] groups() default {};
/**
* #return the payload associated to the constraint
*/
Class<? extends Payload>[] payload() default {};
/**
* #return a class an instance of which will be used to check the existence of a name.
*/
Class<? extends NameExistenceChecker> nameExistenceChecker();
}
Apply this annotation at the class level to the model class:
package com.awgtek.model;
import com.awgtek.model.validation.MyNameExistenceChecker;
import com.awgtek.model.validation.UniqueName;
import com.awgtek.model.validation.UniqueNameConstraint;
#UniqueName(groups = UniqueNameConstraint.class,
nameExistenceChecker = MyNameExistenceChecker.class)
public class ConfigurationAttribute {
private String name;
private String lastName;
// getters and setters omitted
}
The UniqueNameValidator:
package com.awgtek.model.validation;
import com.awgtek.service.MyService;
import javax.inject.Inject;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class UniqueNameValidator implements ConstraintValidator<UniqueName, Object> {
#Inject
private NameExistenceCheckerFactory nameExistenceCheckerFactory;
private NameExistenceChecker nameExistenceChecker;
#Override
public void initialize(UniqueName constraintAnnotation) {
nameExistenceChecker = nameExistenceCheckerFactory.get(constraintAnnotation.nameExistenceChecker());
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
return !nameExistenceChecker.nameExists(value);
}
}
The UniqueNameValidator then depends on a couple of classes:
package com.awgtek.model.validation;
import javax.inject.Inject;
import org.springframework.stereotype.Component;
#Component
public class NameExistenceCheckerFactory {
#Inject
private NamespaceNameExistenceChecker namespaceNameExistenceChecker;
#Inject
private MyNameExistenceChecker myNameExistenceChecker;
public NameExistenceChecker get(Class<? extends NameExistenceChecker> clazz) {
if (clazz.equals(MyNameExistenceChecker.class)) {
return myNameExistenceChecker;
}
throw new IllegalStateException("Unknown NameExistenceChecker");
}
}
package com.awgtek.model.validation;
public interface NameExistenceChecker {
boolean nameExists(Object objectWithName);
}
The implementation class that actually looks up whether the item exists in the database (via a service):
package com.awgtek.model.validation;
import com.awgtek.model.MyModelClass;
import com.awgtek.service.MyService;
import javax.inject.Inject;
import org.springframework.stereotype.Component;
#Component
public class AttributeNameExistenceChecker implements NameExistenceChecker {
#Inject
private MyService myService;
#Override
public boolean nameExists(Object objectWithName) {
MyModelClass myObject = (MyModelClass) objectWithName;
return myService.itemAlreadyExists(myObject.getName(), myObject.getLastName());
}
}
I am just staring with spring-data and spring-data-rest and I really want to take advantage of what these tools have to offer. For the most case the base functionality is perfect for my use case however there are some cases where I need to customize the underlying functionality quite a bit, and selectively assign some repositories to inherit the customized functionality I am after.
To explain the problem a bit better, in spring-data there are 2 possible interfaces which you can inherit functionality from, CrudRepository or PagingAndSortingRepository. I want to add a third called lets say PesimisticRepository
All the PesimisticRepository does is to handle the notion of a deleted #Entity differently. A deleted entity is one where its deleted property is NOT NULL. This means that an #Entity which can be handled by a PesimisticRepository has to have a deleted property.
All this is possible, I have actually implemented this a couple of years ago. (You can check it out here in case you are interested)
My current attempt using spring-data is the following:
An extension of the PagingAndSortingRepository
package com.existanze.xxx.datastore.repositories;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.io.Serializable;
#NoRepositoryBean
public interface PesimisticRepository<T,ID extends Serializable> extends PagingAndSortingRepository<T,ID> {
}
For which I provide a default implementation extending JPARepository
package com.existanze.xxx.datastore.repositories;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.util.Date;
public class JpaPesimisticRepository<T,ID extends Serializable> extends SimpleJpaRepository<T,ID> implements PesimisticRepository<T,ID> {
private final EntityManager entityManager;
public JpaPesimisticRepository(Class<T> domainClass, EntityManager em) {
super(domainClass, em);
this.entityManager = em;
}
#Override
#Transactional
public Page<T> findAll(Specification<T> spec, Pageable pageable) {
CriteriaBuilder cb = this.entityManager.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = cb.createQuery(getDomainClass());
Root<T> from = criteriaQuery.from(this.getDomainClass());
Predicate deleted = cb.equal(from.get("deleted"), cb.nullLiteral(Date.class));
criteriaQuery.select(from).where(deleted);
TypedQuery<T> query = this.entityManager.createQuery(criteriaQuery);
return pageable == null ? new PageImpl<T>(query.getResultList()) : readPage(query, pageable, spec);
}
}
And then for any bean for which I wish to handle the deletion using the pessimistic method I define it as such
package com.existanze.xxx.datastore.repositories;
import com.existanze.xxx.domain.Phone;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
#RepositoryRestResource
public interface PhoneRepository extends PesimisticRepository<Phone,Integer> {
}
It is important to explain why I wish to override these methods instead of providing custom ones, like findAllButDeleted. The reason is because I also want the pessimistic delete to trickle down to spring-data-rest. So that the HTTP endpoints generated will not need any form of customization.
This seems to work only for the findAll method. However for the rest of the methods the current exception is thrown.
$ curl http://localhost:8881/phones/23
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 500 </title>
</head>
<body>
<h2>HTTP ERROR: 500</h2>
<p>Problem accessing /phones/23. Reason:
<pre> org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: object is not an instance of declaring class; nested exception is java.lang.IllegalArgumentException: object is not an instance of declaring class</pre></p>
<hr /><i><small>Powered by Jetty://</small></i>
</body>
</html>
Furthermore, I have read the documentation which allows you to change the default JpaRepository for all Repositories, but again I need to do this on a per repository basis.
I hope I have been descriptive enough. Please let me know in the comments section if there is something that needs better explanation.
You can create a custom repository, something like this:
package com.brunocesar.custom.repository.support;
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.NoRepositoryBean;
import com.brunocesar.custom.entity.CustomAbstractEntity;
#NoRepositoryBean
public interface CustomGenericRepository<E extends CustomAbstractEntity, PK extends Serializable> extends
JpaRepository<E, PK>, JpaSpecificationExecutor<E> {
EntityManager getEntityManager();
}
package com.brunocesar.custom.repository.support.impl;
import java.io.Serializable;
import java.util.Calendar;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import com.brunocesar.custom.entity.CustomAbstractEntity;
import com.brunocesar.custom.repository.support.CustomGenericRepository;
#Transactional(readOnly = true)
public class CustomGenericRepositoryImpl<E extends CustomAbstractEntity, PK extends Serializable> extends
SimpleJpaRepository<E, PK> implements CustomGenericRepository<E, PK> {
private final EntityManager entityManager;
private final JpaEntityInformation<E, ?> entityInformation;
public CustomGenericRepositoryImpl(final JpaEntityInformation<E, ?> entityInformation,
final EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
this.entityInformation = entityInformation;
}
#Override
#Transactional
public void delete(final E entity) {
Assert.notNull(entity, "Entity object must not be null!");
entity.setChangeDate(Calendar.getInstance().getTime());
entity.setDeleted(true);
}
#Override
public List<E> findAll() {
return super.findAll(this.isRemoved());
}
#Override
public E findOne(final PK pk) {
return this.findOne(this.isRemovedByID(pk));
}
private Specification<E> isRemoved() {
return new Specification<E>() {
#Override
public Predicate toPredicate(final Root<E> root, final CriteriaQuery<?> query, final CriteriaBuilder cb) {
return cb.isFalse(root.<Boolean> get("deleted"));
}
};
}
private Specification<E> isRemovedByID(final PK pk) {
return new Specification<E>() {
#Override
public Predicate toPredicate(Root<E> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
final Predicate id = cb.equal(root.get("id"), pk);
final Predicate hidden = cb.isFalse(root.<Boolean> get("deleted"));
return cb.and(id, hidden);
}
};
}
#Override
public EntityManager getEntityManager() {
return this.entityManager;
}
protected JpaEntityInformation<E, ?> getEntityInformation() {
return this.entityInformation;
}
}
You will need too a custom factory bean to setup your custom repositories. Look like this:
package com.brunocesar.custom.repository.support.factory;
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import com.brunocesar.custom.repository.support.impl.CustomGenericRepositoryImpl;
public class CustomGenericRepositoryFactoryBean<T extends JpaRepository<S, ID>, S, ID extends Serializable> extends
JpaRepositoryFactoryBean<T, S, ID> {
#Override
protected RepositoryFactorySupport createRepositoryFactory(final EntityManager entityManager) {
return new RepositoryFactory(entityManager);
}
private static class RepositoryFactory extends JpaRepositoryFactory {
public RepositoryFactory(final EntityManager entityManager) {
super(entityManager);
}
#Override
#SuppressWarnings({"unchecked", "rawtypes"})
protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository(
final RepositoryMetadata metadata, final EntityManager entityManager) {
final JpaEntityInformation<?, Serializable> entityInformation = this.getEntityInformation(metadata
.getDomainType());
return new CustomGenericRepositoryImpl(entityInformation, entityManager);
}
#Override
protected Class<?> getRepositoryBaseClass(final RepositoryMetadata metadata) {
return CustomGenericRepositoryImpl.class;
}
}
}
And finally, the application context configuration.
Common repositories configuration look like this:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="dataSource" p:jpaProperties-ref="jpaProperties" p:jpaVendorAdapter-ref="jpaVendorAdapter"/>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" p:entityManagerFactory-ref="entityManagerFactory" />
<jpa:repositories base-package="com.brunocesar.repository"
transaction-manager-ref="transactionManager" entity-manager-factory-ref="entityManagerFactory" />
And custom, like this (you can or not use separeted EMF and transaction manager):
<bean id="entityManagerFactoryCustom" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="dataSource" p:jpaProperties-ref="jpaProperties" p:jpaVendorAdapter-ref="jpaVendorAdapter"/>
<bean id="transactionManagerCustom" class="org.springframework.orm.jpa.JpaTransactionManager" p:entityManagerFactory-ref="entityManagerFactoryCustom" />
<jpa:repositories base-package="com.brunocesar.custom.repository,com.brunocesar.custom.repository.support"
factory-class="com.brunocesar.custom.repository.support.factory.CustomGenericRepositoryFactoryBean"
transaction-manager-ref="transactionManagerCustom" entity-manager-factory-ref="entityManagerFactoryCustom" />
Example 1, using JpaRepository:
package com.brunocesar.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.brunocesar.entity.CommonEntity;
#Repository
public interface CommonRepository extends JpaRepository<CommonEntity, Long> {
}
Example 2, using custom repository:
package com.brunocesar.custom.repository;
import org.springframework.stereotype.Repository;
import com.brunocesar.custom.entity.CustomEntity;
import com.brunocesar.custom.repository.support.CustomGenericRepository;
#Repository
public interface CustomRepository extends CustomGenericRepository<CustomEntity, Long> {
}
This is part of what I usually do. If you need, I can create a basic application as an example.