Access JpaEntityInformation in custom repository implementation - java

I have some Spring Data repositories that are extended via fragments as described here. This works fine as long as I only inject the EntityManager in this implementations.
One of those implementations is generic and therefor needs an instance of JpaEntityInformation for the current entity to work correctly (I basically only need the entity name and java type). If I try to "autowire" this in the constructor as well, I get an exception that says that no bean of class JpaEntityInformation could be found.
I understand this exception but I'd like to know whether there is another way to get name and class of the entity the current repository instance was created for. I thought that it should be possible to somehow get the JpaEntityInformation via the constructor because this is the way it is done if you specify a custom repository base class (which I don't want to do).
Below you can find a use case for what I just described.
#NoRepositoryBean
#RequiredArgsConstructor
public class FindByFieldRepositoryImpl<T> implements FindByFieldRepository<T> {
private final JpaEntityInformation<T, ?> entityInformation;
private final EntityManager manager;
#Override
public T findByField(String field, Object value) {
return createQuery(field, value).getSingleResult();
}
private TypedQuery<T> createQuery(String fieldName, Object fieldValue) {
String entityName = entityInformation.getEntityName();
Class<T> entityType = entityInformation.getJavaType();
String queryString = String.format("FROM %s WHERE %s = :value", entityName, fieldName);
TypedQuery<T> query = manager.createQuery(queryString, entityType);
return query.setParameter("value", fieldValue);
}
}

You can use JpaEntityInformationSupport to get entity information from its class. Here's how your code would look like:
#NoRepositoryBean
#RequiredArgsConstructor
public class FindByFieldRepositoryImpl<T> implements FindByFieldRepository<T> {
private final EntityManager manager;
#Override
public T findByField(String field, Object value, Class<T> clazz) {
return createQuery(field, value, clazz).getSingleResult();
}
private TypedQuery<T> createQuery(String fieldName, Object fieldValue, Class<T> clazz) {
JpaEntityInformation entityInformation = JpaEntityInformationSupport.getEntityInformation(clazz, manager);
String entityName = entityInformation.getEntityName();
Class<T> entityType = entityInformation.getJavaType();
String queryString = String.format("FROM %s WHERE %s = :value", entityName, fieldName);
TypedQuery<T> query = manager.createQuery(queryString, entityType);
return query.setParameter("value", fieldValue);
}
}
I haven't tested this code but it should work.

Related

How to Enforce MyBatis PSQL custom TypeHandler on property level

I am using a DTO which contains Set<UUID> and Set<String>.
public class MyBatisDTO implements Serializable{
// other attributes
private Set<UUID> uuidSet;
private Set<String> stringSet;
.....
}
A typeHandler has already been registered for Generic-Type Set<T>.
#MappedJdbcTypes(JdbcType.OTHER)
#MappedTypes(Set.class)
public class SetTypeHandler extends BaseTypeHandler<Set<T>> {
#Override
public Set<T> getNullableResult(ResultSet rs, String columnName) throws SQLException {
return mapFromJson(rs.getString(columnName));
}
.......
Now the problem is that when above mentioned MyBatisDTO.java is being mapped from DB values where both columns uuidSet and stringset are stored as jsonb values, typeHandler picks the Generic type as String -> T extends String for Set<UUID> uuidSet; as well which is leading to auto-type conversion to all items of uuidSet as String. This is not an error but in debugging it can be seen that uuidSet contains String values.
Later on, which also leads in de-serialization of JAX-RS response but thats another topic which is not important at the moment.
My question is Is there a way that we can enforce a typeHandler on property's level? so I am looking for a solution in which I may attach custom TypeHandler on property level, something like this
//SUDO_CODE
public class MyBatisDTO implements Serializable{
// other attributes
#TypeHandler ("com.mybatis.handlers.typeHandler.SetUUIDTypeHandler")
private Set<UUID> uuidSet;
#TypeHandler ("com.mybatis.handlers.typeHandler.SetStringTypeHandler")
private Set<String> stringSet;
.....
}
OR if there is a way to mention these typeHandlers on Mapper-Level, something like this?
#Mapper
public interface MyBatisMapper {
//TYPE HANDLER ATTACHMENT/CONFIGURATION HERE ..???
#Select("SELECT uuidSet, stringSet FROM MyBatisDTO WHERE param = #{param}")
Cursor<MyBatisDTO> getData(#Param("param") final UUID param);
.....
}
You can do it with a result map:
#Mapper
public interface MyBatisMapper {
#Select("SELECT uuidSet, stringSet FROM MyBatisDTO WHERE param = #{param}")
#Results({
#Result(column = "uuidSet", property="uuidSet", typeHandler = "com.mybatis.handlers.typeHandler.SetUUIDTypeHandler"),
#Result(column = "stringSet", property="stringSet", typeHandler = "com.mybatis.handlers.typeHandler.SetStringTypeHandler")
})
Cursor<MyBatisDTO> getData(#Param("param") final UUID param);
}

Getting Entity Class in JPA and Repository Interface

I am applying repository similar to Spring Data JPA where I would only create an interface of an entity repository:
public interface AuthorRepository extends Repository<Author, Long> {
}
I have this also Repository interface:
public interface Repository <T, ID extends Serializable> {
List<T> findAll() throws Exception;
}
And its implementation, which I find it difficult to get the class name passed in as parameterized (T) to Repository :
public class RepositoryImpl implements Repository {
#Inject
private EntityManager em;
#Override
public List<Object> findAll() throws Exception {
try {
String namedQuery = "SELECT a FROM " + <How do I get the entity here as Author?> + " a";
TypedQuery<Object> query = em.createNamedQuery(namedQuery, <How do I get the entity class as Author.class?>);
return query.getResultList();
} catch (Exception e) {
System.out.println(e.getMessage());
throw new ApplicationException();
}
}
}
I can't find way how to dynamically generate the entity class (ex. Author) to be created as part of NamedQuery string and an argument for em.createNamequery().
Thanks for any help.
In the RepositoryImpl you can inject the entityInformation like this:
#Autowired
private JpaEntityInformation<T, ID> entityInformation;
and then use it for example like:
String entityName = entityInformation.getEntityName();
Class<T> entityType = entityInformation.getJavaType();
Custom RepositoryFragments sadly can't autowire the JpaEntityInformation because they are singletons, so for generic fragments one would either need to pass the entity class with each method call and use JpaEntityInformationSupport.getEntityInformation(clazz, entityManager) or modify the BeanDefinition of the fragments and get the clazz using the injection point.
Searching world wide web gave me similar approach and codes but none worked but TypeTools works like a charm.

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.

Spring REST partial update with #PATCH method

I'm trying to implement a partial update of the Manager entity based in the following:
Entity
public class Manager {
private int id;
private String firstname;
private String lastname;
private String username;
private String password;
// getters and setters omitted
}
SaveManager method in Controller
#RequestMapping(value = "/save", method = RequestMethod.PATCH)
public #ResponseBody void saveManager(#RequestBody Manager manager){
managerService.saveManager(manager);
}
Save object manager in Dao impl.
#Override
public void saveManager(Manager manager) {
sessionFactory.getCurrentSession().saveOrUpdate(manager);
}
When I save the object the username and password has changed correctly but the others values are empty.
So what I need to do is update the username and password and keep all the remaining data.
If you are truly using a PATCH, then you should use RequestMethod.PATCH, not RequestMethod.POST.
Your patch mapping should contain the id with which you can retrieve the Manager object to be patched. Also, it should only include the fields with which you want to change. In your example you are sending the entire entity, so you can't discern the fields that are actually changing (does empty mean leave this field alone or actually change its value to empty).
Perhaps an implementation as such is what you're after?
#RequestMapping(value = "/manager/{id}", method = RequestMethod.PATCH)
public #ResponseBody void saveManager(#PathVariable Long id, #RequestBody Map<Object, Object> fields) {
Manager manager = someServiceToLoadManager(id);
// Map key is field name, v is value
fields.forEach((k, v) -> {
// use reflection to get field k on manager and set it to value v
Field field = ReflectionUtils.findField(Manager.class, k);
field.setAccessible(true);
ReflectionUtils.setField(field, manager, v);
});
managerService.saveManager(manager);
}
Update
I want to provide an update to this post as there is now a project that simplifies the patching process.
The artifact is
<dependency>
<groupId>com.github.java-json-tools</groupId>
<artifactId>json-patch</artifactId>
<version>1.13</version>
</dependency>
The implementation to patch the Manager object in the OP would look like this:
Controller
#Operation(summary = "Patch a Manager")
#PatchMapping("/{managerId}")
public Task patchManager(#PathVariable Long managerId, #RequestBody JsonPatch jsonPatch)
throws JsonPatchException, JsonProcessingException {
return managerService.patch(managerId, jsonPatch);
}
Service
public Manager patch(Long managerId, JsonPatch jsonPatch) throws JsonPatchException, JsonProcessingException {
Manager manager = managerRepository.findById(managerId).orElseThrow(EntityNotFoundException::new);
JsonNode patched = jsonPatch.apply(objectMapper.convertValue(manager, JsonNode.class));
return managerRepository.save(objectMapper.treeToValue(patched, Manager.class));
}
The patch request follows the specifications in RFC 6092, so this is a true PATCH implementation. Details can be found here
With this, you can patch your changes
1. Autowire `ObjectMapper` in controller;
2. #PatchMapping("/manager/{id}")
ResponseEntity<?> saveManager(#RequestBody Map<String, String> manager) {
Manager toBePatchedManager = objectMapper.convertValue(manager, Manager.class);
managerService.patch(toBePatchedManager);
}
3. Create new method `patch` in `ManagerService`
4. Autowire `NullAwareBeanUtilsBean` in `ManagerService`
5. public void patch(Manager toBePatched) {
Optional<Manager> optionalManager = managerRepository.findOne(toBePatched.getId());
if (optionalManager.isPresent()) {
Manager fromDb = optionalManager.get();
// bean utils will copy non null values from toBePatched to fromDb manager.
beanUtils.copyProperties(fromDb, toBePatched);
updateManager(fromDb);
}
}
You will have to extend BeanUtilsBean to implement copying of non null values behaviour.
public class NullAwareBeanUtilsBean extends BeanUtilsBean {
#Override
public void copyProperty(Object dest, String name, Object value)
throws IllegalAccessException, InvocationTargetException {
if (value == null)
return;
super.copyProperty(dest, name, value);
}
}
and finally, mark NullAwareBeanUtilsBean as #Component
or
register NullAwareBeanUtilsBean as bean
#Bean
public NullAwareBeanUtilsBean nullAwareBeanUtilsBean() {
return new NullAwareBeanUtilsBean();
}
First, you need to know if you are doing an insert or an update. Insert is straightforward. On update, use get() to retrieve the entity. Then update whatever fields. At the end of the transaction, Hibernate will flush the changes and commit.
You can write custom update query which updates only particular fields:
#Override
public void saveManager(Manager manager) {
Query query = sessionFactory.getCurrentSession().createQuery("update Manager set username = :username, password = :password where id = :id");
query.setParameter("username", manager.getUsername());
query.setParameter("password", manager.getPassword());
query.setParameter("id", manager.getId());
query.executeUpdate();
}
ObjectMapper.updateValue provides all you need to partially map your entity with values from dto.
As an addition, you can use either of two here: Map<String, Object> fields or String json, so your service method may look like this:
#Autowired
private ObjectMapper objectMapper;
#Override
#Transactional
public Foo save(long id, Map<String, Object> fields) throws JsonMappingException {
Foo foo = fooRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));
return objectMapper.updateValue(foo , fields);
}
As a second solution and addition to Lane Maxwell's answer you could use Reflection to map only properties that exist in a Map of values that was sent, so your service method may look like this:
#Override
#Transactional
public Foo save(long id, Map<String, Object> fields) {
Foo foo = fooRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));
fields.keySet()
.forEach(k -> {
Method method = ReflectionUtils.findMethod(LocationProduct.class, "set" + StringUtils.capitalize(k));
if (method != null) {
ReflectionUtils.invokeMethod(method, foo, fields.get(k));
}
});
return foo;
}
Second solution allows you to insert some additional business logic into mapping process, might be conversions or calculations ect.
Also unlike finding reflection field Field field = ReflectionUtils.findField(Foo.class, k); by name and than making it accessible, finding property's setter actually calls setter method that might contain additional logic to be executed and prevents from setting value to private properties.

Spring Generic Dao class name

I have configured a custom generic service DAO for my spring / hibernate project - the idea being that I can reuse it easily from my controllers.
It essentially looks like this:
public class DefaultService<T> {
private Class<T> e;
public String className(Class<T> e) {
String clip = e.getName();
clip = clip.substring(clip.lastIndexOf('.') + 1, clip.length());
return clip;
}
public List<T> getAll(Integer status) {
Session session = sessionFactory.getCurrentSession();
Query query = session.createQuery("FROM " + className(e) + " WHERE status = " + status);
return query.list();
}
...
Which gets referenced by:
#Autowired
public DefaultService<Address> addressService;
addressService.get(1);
However the String clip = e.getName() line throws a Null pointer exception. I can get this to work if I move the class into the attributes section (so addressService.get(Address.class, 1) but I find this somewhat untidy, especially when there are multiple different classes being called upon.
Is there some way to get the class to generate a value correctly without repeatedly adding it into all my functions?
Thanks in advance.
I did something similar, you need the generic class to be a constructor argument as well, mine uses hibernate entities, but you could pass in the string of table name.
public class DomainRepository<T> {
#Resource(name = "sessionFactory")
protected SessionFactory sessionFactory;
public DomainRepository(Class genericType) {
this.genericType = genericType;
}
#Transactional(readOnly = true)
public T get(final long id) {
return (T) sessionFactory.getCurrentSession().get(genericType, id);
}
You can then subclass (if you need to) to customize or simply set up you bean in the spring config like below t :
<bean id="tagRepository" class="com.yourcompnay.data.DomainRepository">
<constructor-arg value="com.yourcompnay.domain.Tag"/>
</bean>
So in your code you could then reference tagRepository like so (no other cod eis needed than that posted above, and below) :
#Resource(name = "tagRepository")
private DomainRepository<Tag> tagRepository;
Also, I would call it a repository not a service, a service deals with different types and their interactions (not just one). And for specifically your example using SQL strings :
public final String tableName;
public DomainRepository(String tableName) {
this.tableName = tableName;
}
public List<T> getAll(Integer status) {
Session session = sessionFactory.getCurrentSession();
Query query = session.createQuery("FROM " + tableName + " WHERE status = " + status);
return query.list();
}
and have your beans defined like so
<bean id="addressRepository" class="com.yourcompnay.data.DomainRepository">
<constructor-arg value="address"/>
</bean>
And then you can alsow create subclasses youself where necessary :
public class PersonRepository extends DomainRepository<Person> {
public PersonRepository(){
super("person"); //assumes table name is person
}
As I understand you got NPE because you did not set any value for this field.
So you can resolve this problem by 2 ways:
Set manually class object as in comment NimChimpsky.
Get class type dynamically. E.g, if you use Spring try this one:
protected Class getEntityClass() {
return GenericTypeResolver.resolveTypeArguments(getClass(), DefaultService.class)[0];
}
or some workaround here
It's better to define a specific class for Address service
public class AddressService extends DefaultService<Address>{
public String getClassName(){
return "Address";
}
}
where
public String getClassName();
is an abstract method declared in DefaultService, and used (like your method className()) in your data access logic.
Using this approach, you will be able to add specific data access logic (example, getUsersByAddress)

Categories