I have a bunch of tables for which I need to provide standard CRUD interface. Every time I have to expose a new table, I follow the below pattern.
public interface EntityWithId<TDbEntity> extends Serializable {
public TDbEntity entityId();
}
#Entity
public class DbEntityName implements EntityWithId<Long> {
#Id private Long id;
#Override public Long entityId() {return id;}
// other fields follow
}
public class EntityName {
private Long id;
// other fields follow
// create Entity from DbEntity
public EntityName(DbEntityName dbItem) { ... }
// get DbEntity from Entity
public DbEntityName toDb() { ... }
}
#Repository
public interface DbEntityNameRepository extends CrudRepository<DbEntityName, Long> { }
public interface CrudService<TDbEntity extends EntityWithId<ID>, ID> {
CrudRepository<TDbEntity, ID> getCrudRepository();
// provide default implementation of all CRUD operations here like the below one
default TDbEntity save(TDbEntity entity) { return getCrudRepository().save(entity); }
}
public interface DbEntityNameService extends CrudService<DbEntityName, Long> {
}
#Service
public class DbEntityNameServiceImpl implements DbEntityNameService {
#lombok.Getter #Autowired DbEntityNameRepository crudRepository;
}
#RestController
#RequestMapping("/api/v1/dbservice")
public class EntityNameController {
#Autowired DbEntityNameService dbService;
#PostMapping("/{EntityName}") // this should be replaced by the actual name of the entity
public Long save(#RequestBody EntityName msg) {
return dbService.save(msg.toDb()).entityId();
}
// implement other CRUD end points
}
EntityWithId<T> interface and CrudService<TDbEntity extends EntityWithId<ID>, ID> are defined only once for the system. They provide mechanism to get rid of repeat code in accessing the repo.
As you will notice, the only real code needs to be done to add the fields in the Entity and the DB Entity, and their conversion. Also, I need to roll a new Controller out for each new table.
Question: How can I structure the controller code, in a way that I can inherit the functionality from a base CRUD controller.
Note that, in my real code not all entities are for simple CRUD, and the current structure provides easy way to extend the services
In a nutshell, I am looking for some pattern that will help me provide something like below, where I have a generic Base class, and I can create a subclass with minimal code to expose the controller's end point. Needless to say, the below code will not work as-is to provide the functionality I am looking for.
class BaseController<TEntity, TDbEntity, TId> {
CrudService<TDbEntity, TId> dbService;
#GetMapping("/{TEntity}/{id}")
public TEntity getById(#PathVariable TId id) {
return new TEntity(dbService.getById(id));
}
#PostMapping("/{TEntity}")
public Long save(#RequestBody TEntity msg) {
return dbService.save(msg.toDb()).entityId();
}
}
class EntityNameController : BaseController<EntityName, DbEntityName, Long> {
}
Feel free to provide other suggestions too. My intention is to reduce repeated code in the controller - which is primarily creating the CRUD function, associating it with a CRUD endpoint and invoking the underlying service to do the real work.
EDIT: I understand that I can write a custom annotation processor to generate the standard CRUD functions (almost like how CrudRepository works), but that is not the direction I want to go.
Just to clarify, the intention here is that the standard functionality (like CRUD) can be coded once and for all in a base controller which will expose it, freeing up the child controller to take care of other non-standard work.
I would think for the dbService you could use something like
public interface CrudService<T, ID> {
T findByName(String name);
Set<T> findAll();
T findById(ID id);
T save(T object);
void delete(T object);
void deleteById(ID id);
}
public interface EntityNameService extends CrudService<EntityName, Long> {
}
public class EntityNameServiceImpl implements EntityNameService {
#Inject
private DbEntityNameRepository repository;
// implement all your repo code here
}
And your Base Controller could start like
public class BaseController {
#Autowired
private EntityNameService service;
public String getEntityName(String name) {
service.findByName(name);
}
#PostMapping("/{EntityName}") // this should be replaced by the actual name of the entity
public Long save(#PathVariable String EntityName) {
getEntityName(EntityName);
return dbService.save(msg.toDb()).entityId();
}
}
This was an attempt at getting rid of some boiler plate. The idea was that the business logic would sit in the service and not in the RestController or Repository.
A service could be reused and unit tested well.
QueryDSL with SpringData is your friend:
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
A base repo.
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
#NoRepositoryBean
public interface BaseRepo<T , ID > extends PagingAndSortingRepository<T, ID>, QuerydslPredicateExecutor<T> {}
The real repository that can access very large tables:
import com.querydsl.core.types.Predicate;
import static java.lang.System.out;
import refactor.BaseRepo;
public interface MyEntityRepository extends BaseRepo<MyEntity, String> {
#Override
default long count(Predicate predicate){
//counts on very large tables take forever. Optionally add this
return 0;
}
#Override
default long count(){
//counts on very large tables take forever. Optionally add this
return 0;
}
}
The base service:
import com.querydsl.core.types.Predicate;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
#RequiredArgsConstructor
public class BaseService<T, ID> {
final BaseRepo<T, ID> repo;
public Page<T> findAll(Predicate predicate, Pageable pageable) {
return repo.findAll(predicate, pageable);
}
public Iterable<T> findAllWithoutMeta(Predicate predicate, Pageable pageable) {
return repo.findAll(predicate, pageable);
}
public Iterable<T> findAll() {
return repo.findAll();
}
public T save(T vendor) {
return repo.save(vendor);
}
public T update(T vendor) {
return repo.save(vendor);
}
public void delete(ID id) {
repo.deleteById(id);
}
public boolean exists(ID id) {
return repo.findById(id).isPresent();
}
public Optional<T> getById(ID id) {
return repo.findById(id);
}
}
The real service:
import com.querydsl.core.types.Predicate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
#Service
public class MyService extends BaseService<MyEntity, String>{
public MyService(MyEntityRepository repo) {
super(repo);
}
#Override
public Page<MyEntity> findAll(Predicate predicate, Pageable pageable) {
return super.findAll(predicate, pageable);
}
}
I decided not to genrify my RestContoller and only write what I code I needed for the CRUD operations I needed. (In some cases delete and put operations are not needed or wanted for example)
This is an implementation of a HATEOAS RESTful API. Investing in a HATEOAS design is not for everyone and every application. This can be substituted by a plain rest controller.
The get here can filter on all the fields in the repository. So you can get http://localhost/api/v1/myapi?name=YourName&age=30
import com.querydsl.core.types.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Pageable;
import org.springframework.data.querydsl.binding.QuerydslPredicate;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping(path = "/api/v1/myapi", produces = MediaTypes.HAL_JSON_VALUE)
public class MyApiController {
private final MyService service;
private final EntityLinks eLinks;
MyApiController(MyService service, EntityLinks eLinks) {
this.service = service;
this.eLinks = eLinks;
}
#GetMapping
#Transactional(readOnly = true)
ResponseEntity<Resources<Resource<MyEntity>>> findAll(#QuerydslPredicate(root = MyEntity.class) Predicate predicate, Pageable pageable) {
return new ResponseEntity(toResources(service.findAllWithoutMeta(predicate,pageable)), HttpStatus.OK);
}
#GetMapping(value = "/{id}")
ResponseEntity<Resource<MyEntity>> findOne(#PathVariable String id) {
final Optional<MyEntity> findById = service.getById(id);
if (!findById.isPresent()) {
return null;//fixme ResponseEntity.notFound(assembler.);
}
return ResponseEntity.ok(toResource(findById.get()));
}
private Resources<Resource<MyEntity>> toResources(Iterable<MyEntity> customers) {
List<Resource<MyEntity>> customerResources = new ArrayList<>();
for (MyEntity l : customers) {
customerResources.add(toResource(l));
}
return new Resources<>(customerResources);//, selfLink);
}
private Resource<MyEntity> toResource(MyEntity customer) {
Link selfLink = linkTo(methodOn(CallLoggingController.class).findOne(customer.getId())).withSelfRel();
return new Resource<>(customer, selfLink);
}
}
My advice is do not to be obsessed with generic code. Copy and paste is better than super generic code imho.
Related
I have a Spring Boot application and I have implemented a base controller that handles CRUD operations for all my entities. I have also created a BrandController that extends the base controller and a BrandRepository that implements CrudRepository. The problem is that when I try to access the endpoints for the BrandController such as /api/brands, I get a 404 error, but I can access them on /brands How can I fix this so that the endpoints are accessible with /api/entitys?
Here is the code for the BrandController:
package parc.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import parc.model.concrete.Brand;
import parc.repository.BrandRepository;
#RestController
#RequestMapping("/api/brands")
public class BrandController extends BaseController<Brand, BrandRepository> {
private final BrandRepository repository;
public BrandController(BrandRepository repository) {
super(repository);
this.repository = repository;
}
}
Here is the code for the BaseController:
package parc.controller;
import org.springframework.data.repository.CrudRepository;
import org.springframework.web.bind.annotation.*;
import java.util.List;
public class BaseController<T, R extends CrudRepository<T, Long>> {
private R repository;
public BaseController(R repository) {
this.repository = repository;
}
#GetMapping("/")
public List<T> getAll() {
return (List<T>) repository.findAll();
}
#PostMapping("/")
public T create(#RequestBody T entity) {
return repository.save(entity);
}
#GetMapping("/{id}")
public T getById(#PathVariable long id) {
return repository.findById(id).orElse(null);
}
#PutMapping("/{id}")
public T update(#PathVariable long id, #RequestBody T entity) {
return repository.save(entity);
}
#DeleteMapping("/{id}")
public void delete(#PathVariable long id) {
repository.deleteById(id);
}
}
And finally the code for the BrandRepository:
package parc.repository;
import org.springframework.data.repository.CrudRepository;
import parc.model.concrete.Brand;
public interface BrandRepository extends CrudRepository<Brand, Long> {
}
I'm not a pro in Spring Boot so I'll appreciate any kind of help!
What do you have in application.yml?
maybe setting the following code will work:
in application.yml:
server:
contextPath: /api
and the BrandController:
package parc.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import parc.model.concrete.Brand;
import parc.repository.BrandRepository;
#RestController
#RequestMapping("/brands")
public class BrandController extends BaseController<Brand, BrandRepository> {
private final BrandRepository repository;
public BrandController(BrandRepository repository) {
super(repository);
this.repository = repository;
}
}
I realize that these are internal APIs, but if they're available internally why not make them usable by the less privileged masses, and they're also extremely useful. Even though these APIs were internal in Jersey 2.25 they could be used, and I'd like to upgrade my Jersey version without breaking my custom Jersey extensions.
It's certainly possible to extend ValueParamProvider in Jersey 2.27, but I no longer see a way to register that Provider along with it's triggering annotation. Looking at how Jersey does this for its own implementations, it now uses a BoostrapConfigurator, which seems to be internalized to such an extent that external implementations can't use the same methodology.
Maybe I'm wrong about that, and if someone has a clear description of how, that would be great. Otherwise, does anyone know of a method for doing the same thing?
This used to work...
ResourceConfig resourcceConfig = ...
resourceConfig.register(new AbstractBinder() {
#Override
protected void configure (){
bind(MyParamValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
bind(MyParamInjectionResolver.class).to(new TypeLiteral<InjectionResolver<EntityParam>>() {
}).in(Singleton.class);
}
}
});
With appropriate implementations of AbstractValueFactoryProvider and ParamInjectionResolver.
Now it looks like you need to implement ValueParamProvider, which is easy enough, but I'm not sure how to register that properly with the Jersey framework anymore. Any help appreciated.
You don't need to use any BootstrapConfigurator. All you need to is add the services to the injector and they will be added later to the list of value providers.
To configure it, you can still use the AbstractBinder, but instead of the HK2 one, use the Jersey one. The ValueParamProvider can still be bound the same way, but for the InjectionResolver, you should make sure to implement not the HK2 resolver, but the Jersey one. Then instead of binding to TypeLiteral, bind to GenericType.
I just want to add that a misconception that people have when trying to implement parameter injection is that we also need an InjectResolver to use a custom annotation for the method parameter. This is not the case. The method parameter annotation is just a marker annotation that we should check inside ValueParamProvider#getValueProvider() method. An InjectResolver is only needed for non-method-parameter injections, for instance field and constructor injection. If you don't need that, then you don't need the InjectionResolver.
Below is a complete example using Jersey Test Framework. I didn't use an InjectionResolver, just to show that it's not needed.
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.model.Parameter;
import org.glassfish.jersey.server.spi.internal.ValueParamProvider;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.inject.Singleton;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.core.Response;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThat;
public class ParamInjectTest extends JerseyTest {
#Target({ElementType.PARAMETER, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface Auth {
}
private static class User {
private String username;
public User(String username) {
this.username = username;
}
public String getUsername() {
return this.username;
}
}
public static class AuthValueParamProvider implements ValueParamProvider {
#Override
public Function<ContainerRequest, ?> getValueProvider(Parameter parameter) {
if (parameter.getRawType().equals(User.class)
&& parameter.isAnnotationPresent(Auth.class)) {
return new UserParamProvider();
}
return null;
}
private class UserParamProvider implements Function<ContainerRequest, User> {
#Override
public User apply(ContainerRequest containerRequest) {
return new User("Peeskillet");
}
}
#Override
public PriorityType getPriority() {
return Priority.HIGH;
}
}
public static class AuthFeature implements Feature {
#Override
public boolean configure(FeatureContext context) {
context.register(new AbstractBinder() {
#Override
protected void configure() {
bind(AuthValueParamProvider.class)
.to(ValueParamProvider.class)
.in(Singleton.class);
}
});
return true;
}
}
#Path("test")
#Consumes("text/plain")
public static class TestResource {
#POST
#Produces("text/plain")
public Response post(String text, #Auth User user) {
return Response.ok(user.getUsername() + ":" + text).build();
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig()
.register(TestResource.class)
.register(AuthFeature.class);
}
#Test
public void testIt() {
final Response response = target("test")
.request()
.post(Entity.text("Test"));
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.readEntity(String.class)).isEqualTo("Peeskillet:Test");
}
}
Another thing I'll mention is that in previous versions where you extended AbstractValueFactoryProvider and implemented a ParamInjectionResolver, most people did this to follow how Jersey implemented parameter injection while still allowing for other injection points (field and constructor). If you still want to use this pattern, you can.
Below is the AuthFeature from the above test refactored
public static class AuthFeature implements Feature {
#Override
public boolean configure(FeatureContext context) {
InjectionManager im = InjectionManagerProvider.getInjectionManager(context);
AuthValueParamProvider authProvider = new AuthValueParamProvider();
im.register(Bindings.service(authProvider).to(ValueParamProvider.class));
Provider<ContainerRequest> request = () -> {
RequestProcessingContextReference reference = im.getInstance(RequestProcessingContextReference.class);
return reference.get().request();
};
im.register(Bindings.injectionResolver(new ParamInjectionResolver<>(authProvider, Auth.class, request)));
return true;
}
}
I figured this stuff out just digging through the source. All this configuration I saw in the ValueParamProviderConfigurator. You don't need to implement your own ParamInjectionResolver. Jersey has a concrete class already that we can just use, as done in the feature above.
If you change the TestResource to inject by field, it should work now
#Path("test")
#Consumes("text/plain")
public static class TestResource {
#Auth User user;
#POST
#Produces("text/plain")
public Response post(String text) {
return Response.ok(user.getUsername() + ":" + text).build();
}
}
I started to develop APIs for iOS app using Java Spring MVC two months ago.
I'll explain my question with an example for clarification purpose.
Let's say I want to update a user's name.
My mySQL user table has columns: id, user_id, email, display_name.
My approach was:
define user:
User.java:
package bean;
public class User {
String displayName;
String email;
String userId;
getters/setters...
}
2.define a DAO:
UserDao.java:
package dao;
import bean.StandardResponse;
public interface UserDao {
public StandardResponse updateUserName(String user_id,String userName);
}
UserDaoImpl.java:
package implement;
import bean.User;
import common.DatabaseConnect;
public UserDaoImpl implements UserDao {
private DatabaseConnect dbConnect;
public UserDaoImpl(DatabaseConnect dbConnect) {
this.dbConnect = dbConnect;
}
public StandardResponse updateUserName(userId,userName) {
if ((userId == null || userId.isEmpty()) ||(userName == null || userName.isEmpty())) return new StandardResponse("Error","Parameters not set!");
String sql = null;
Statement smt = dbConnect.createDataBaseConnectResourse();
StandardResponse result = new StandardResponse("Error","Fail to update the record!");
sql = "update User set user_name="+userName+" where user_id='"+userId+"'";
int updateResult = 0;
try {
updateResult = smt.executeUpdate(sql);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
dbConnect.closeDatabaseConnectResource();
}
if (updateResult == 1) {
result = new StandardResponse("Success","The record has been updated!");
}
return result;
}
}
3.controller
import org.springframework.stereotype.Controller;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import bean.StandardResponse;
import bean.User;
import common.DatabaseConnect;
import common.SpringApplicationContext;
import dao.UserDao;
import implement.UserDAOImpl;
#Controller
#RestController
#RequestMapping("user")
public class UserController {
DatabaseConnect dbConnect = SpringApplicationContext.getApplicationContext().getBean("databaseConnect", DatabaseConnect.class);
UserDao uiObject = new UserDaoImpl(dbConnect);
#RequestMapping(value = "/updateUserName", method = RequestMethod.POST)
public StandardResponse updateUserName(HttpServletRequest request, HttpServletResponse reponses, Model model) {
StandardResponse srObject = uiObject.updateUserName(request.getparameter("userId"),request.getParameter("userName"));
return srObject;
}
}
I just put the crucial classes here. I believe you can understand what I am doing here. So if someone access the URL:****/user/updateUserName providing the userId and userName, he can update the user name of that record. It is functionalbe.
I used the same approach and finished the whole project. It is working. Then, I asked an experienced programmer to look at my code since I figured out all these based on my own understanding. I would like to know how did they do in industry. He gave me some valuable comments.
The whole structure is wrong. I shouldn't have logics in my dao. I should at least have dao, service and action layers. dao only handles database access, service handles all the logic and action handels communication and decide which service to call.
It is very bad approach to hand written SQL in the code. I should use Hibernate.
I should use control inverse and dependency injection.(I am not dealing with this in this post.)
So I started to update my codes to use Hibernate.
define user:
User.java:
package model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name="User")
public class User {
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#Column(name = "user_id")
private String userId;
#Column(name = "user_name")
private String displayName;
#Column(name = "email")
private String emai;
all getters/setters...
}
dao:
UserDao.java:
package dao;
import model.User;
import java.util.List;
public interface UserDAO {
public void updateUser(User p);
other methods....
}
UserDaoImpl.java
package dao;
import model.User;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Repository;
import model.User;
#Repository
public class PersonDAOImpl implements PersonDAO {
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sf){
this.sessionFactory = sf;
}
#Override
public void updatePerson(Person p) {
Session session = this.sessionFactory.getCurrentSession();
session.update(p);
}
implementations of other methods....
}
service:
UserService.java:
package service;
import java.util.List;
import model.User;
public interface UserService {
public void updateUser(User p);
other methods....
}
UserServiceImpl.java:
package service;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import dao.UserDAO;
import model.User;
#Service
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
#Override
#Transactional
public void updateUser(User p) {
this.userDAO.updateUser(p);
}
implementation of other methods...
}
Now, I just need to write a class to handle the request and call this updateUser service. The whole structure looks better than mine. However, the request won't gave me the whole user object. All I can get is user_id and user_name. I am also not allowed to put logics in dao. So I have to query the table first to get the whole user object and then update the user_name. Comparing to what I used to do, one SQL handles the update. Now using Hibernate, I need 1 query and 1 update.
I found a solution on StackOverflow that I can use HQL to do this in one step. But I though the purpose for using Hibernate is to free us from writing the SQL. If I need to write HQL, why don't I just keep using the writing SQL approach.
So is there a way to do update with Hibernate without query the table first in this structure? Or this is a trade-off for better structure?
I am sorry that I used a really long example for this question. But I couldn't think of other ways to explain the whole story clearly.
So is there a way to do update with Hibernate without query the table first in this structure? Or this is a trade-off for better structure?
Thereis NO tradeoff, you can do updates with HQL(Hibernate Query Language) as well like how you do in SQL.
You can look at the following code:
UserDAOImpl class:
#Repository
//you are calling this in ur code as PersonDAOImpl..change it to UserDAOImpl
public class UserDAOImpl implements UserDAO {
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sf){
this.sessionFactory = sf;
}
#Override
public int updateUser(String userId, String userName) {
Session session = this.sessionFactory.getCurrentSession();
Query query = session.createQuery("update User set userName =:userName where userId = :userName ");
query.setString("userName", userName);
query.setString(userName, userName);
int result = query.executeUpdate();
return result;
}
}
UserServiceImpl class:
#Service
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
#Override
#Transactional
public void updateUserName(String userId, String userName) {
if(userId !=null && userName != null) {
int result = this.userDAO.updateUser(userId, userName);
if(result==0) //userid not available {
//if userid is NOT available, what to do? check your requirement and handle properly
}
}
} else {
//throw exception
}
}
implementation of other methods...
}
Controller Class:
#Controller
#RestController
#RequestMapping("user")
public class UserController {
#Autowired
private UserService userService;
#RequestMapping(value = "/updateUserName", method = RequestMethod.POST)
public StandardResponse updateUserName(HttpServletRequest request, HttpServletResponse reponses, Model model) {
StandardResponse srObject = userService.updateUserName(request.getparameter("userId"),request.getParameter("userName"));
return srObject;
}
}
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)
On this moment I'm dealing with a tricky problem. For my API I was hoping to use Play and Akka actor. The problem I'm having is that every object I try to inject in my Actor remains null. One solution is to inject this object in the controller and than pass it on to my actor but this is not what I want to do. I want my object only on the place where I need it.
package actors;
import actors.Messages.GetAanleverAfspraakById;
import akka.actor.UntypedActor;
import model.domain.AanleverAfspraakDO;
import play.db.jpa.JPAApi;
import javax.inject.Inject;
import javax.persistence.Query;
import java.util.Collection;
/**
* Created by harms.h on 22-03-2016.
*/
public class AfspraakActor extends UntypedActor {
#Inject
private JPAApi api;
#Override
public void onReceive(Object message) throws Exception {
if(message instanceof GetAanleverAfspraakById){
final AanleverAfspraakDO aanleverAfspraakDO = this.getAanleverAfspraakDO(((GetAanleverAfspraakById) message).getId());
getSender().tell(aanleverAfspraakDO, getSelf());
}
else{
unhandled(message);
}
}
private AanleverAfspraakDO getAanleverAfspraakDO(int id){
final AanleverAfspraakDO aanleverAfspraakDO = api.withTransaction(() -> {
final Query query = api.em().createNamedQuery("findbyid").setParameter("id", id);
final Collection<AanleverAfspraakDO> resultSet = query.getResultList();
final AanleverAfspraakDO result = resultSet.iterator().next();
return result;
});
return aanleverAfspraakDO;
}
}
What am I doing wrong here?
For now I used the following code after a long search on the internet
this.api = Play.current().injector().instanceOf(JPAApi.class);
I'm not sure if this is a clean solution what do you guys think?
Play 2.5: Create the following class:
import javax.inject.Inject;
import akka.actor.Actor;
import akka.actor.IndirectActorProducer;
import akka.actor.UntypedActor;
import play.api.Play;
public class GenericDependencyInjector implements IndirectActorProducer {
final Class<? extends UntypedActor> actorClass;
#Inject
public GenericDependencyInjector(Class<? extends UntypedActor> actorClass) {
this.actorClass = actorClass;
}
#Override
public Class<? extends Actor> actorClass() {
return actorClass;
}
#Override
public Actor produce() {
return Play.current().injector().instanceOf(actorClass);
}
}
Then when you create the actor, pass the GenericDependencyInjector along with your Service Interface:
final Props props = Props.create(GenericDependencyInjector.class, JPAApi.class);
context().actorOf(props);
Now you should be able inject your services into the actor with ease.