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.
Related
Good morning, I am new to Spring Boot, and I am performing a rest service that must invoke a procedure stored in the database, the question is that you receive the mobile and must return a code and result, as shown below:
This is my code:
Main Class
package com.app.validacion;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
Controller
package com.app.validacion.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.app.validacion.dao.DriverBonificadosRepository;
import com.app.validacion.entity.RespuestaVo;
#RestController
public class DriverBonificadosController {
#Autowired
private DriverBonificadosRepository dao;
#GetMapping("/service/{movil}")
public RespuestaVo ConsultarMovil(#PathVariable String movil) {
return dao.validarClienteBonifiado(movil);
}
}
Repository
package com.app.validacion.dao;
import org.springframework.data.jpa.repository.query.Procedure;
import org.springframework.data.repository.CrudRepository;
import com.app.validacion.entity.DriverBonificados;
import com.app.validacion.entity.RespuestaVo;
public interface DriverBonificadosRepository extends CrudRepository<DriverBonificados, Integer> {
#Procedure(procedureName="ValidacionClienteBonificado")
RespuestaVo validarClienteBonifiado(String pMovil);
}
My entity
import javax.persistence.NamedStoredProcedureQueries;
import javax.persistence.NamedStoredProcedureQuery;
import javax.persistence.ParameterMode;
import javax.persistence.StoredProcedureParameter;
import javax.persistence.Table;
#NamedStoredProcedureQueries({
#NamedStoredProcedureQuery(
name="SPValidationClienteBonus4G",
procedureName="ValidacionClienteBonificado",
parameters = {
#StoredProcedureParameter(mode=ParameterMode.IN, name="p_movil",type=String.class),
#StoredProcedureParameter(mode=ParameterMode.OUT, name="code",type=String.class),
#StoredProcedureParameter(mode=ParameterMode.OUT, name="result",type=String.class),
})
})
#Entity
#Table
public class DriverBonificados {
#Id
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getMovil() {
return movil;
}
public void setMovil(String movil) {
this.movil = movil;
}
public String getContador() {
return contador;
}
public void setContador(String contador) {
this.contador = contador;
}
public Date getFecha_driver() {
return fecha_driver;
}
public void setFecha_driver(Date fecha_driver) {
this.fecha_driver = fecha_driver;
}
public Date getFecha_alta() {
return fecha_alta;
}
public void setFecha_alta(Date fecha_alta) {
this.fecha_alta = fecha_alta;
}
public Date getFecha_fin() {
return fecha_fin;
}
public void setFecha_fin(Date fecha_fin) {
this.fecha_fin = fecha_fin;
}
public Date getCodigo_transaccion() {
return codigo_transaccion;
}
public void setCodigo_transaccion(Date codigo_transaccion) {
this.codigo_transaccion = codigo_transaccion;
}
private String movil;
private String contador;
private Date fecha_driver;
private Date fecha_alta;
private Date fecha_fin;
private Date codigo_transaccion;
My Class RespuestaVo
package com.app.validacion.entity;
public class RespuestaVo {
private String code;
private String result;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
}
And I get the following error (the mobile parameter must be received as a String, since in the database it is found as Varchar):
Anyone have an idea how this problem could be solved? I need to consult via Stored Procedue if or if
UPDATE
Using #Query and modifying the code as follows:
package com.app.validacion.dao;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.query.Procedure;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import com.app.validacion.entity.DriverBonificados;
import com.app.validacion.entity.RespuestaVo;
public interface DriverBonificadosRepository extends CrudRepository<DriverBonificados, Integer> {
#Query(nativeQuery = true,value = "call ValidacionClienteBonificado(:movil)")
RespuestaVo validarClienteBonifiado(#Param("movil") String pMovil);
}
I get the following error:
org.springframework.core.convert.ConverterNotFoundException: No
converter found capable of converting from type
[org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap]
to type [com.app.validacion.entity.RespuestaVo] at
org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:321)
~[spring-core-5.2.1.RELEASE.jar:5.2.1.RELEASE] at
org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:194)
~[spring-core-5.2.1.RELEASE.jar:5.2.1.RELEASE] at
org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:174)
~[spring-core-5.2.1.RELEASE.jar:5.2.1.RELEASE] at
org.springframework.data.repository.query.ResultProcessor$ProjectingConverter.convert(ResultProcessor.java:297)
~[spring-data-commons-2.2.1.RELEASE.jar:2.2.1.RELEASE] at
org.springframework.data.repository.query.ResultProcessor$ChainingConverter.lambda$and$0(ResultProcessor.java:217)
~[spring-data-commons-2.2.1.RELEASE.jar:2.2.1.RELEASE] at
org.springframework.data.repository.query.ResultProcessor$ChainingConverter.convert(ResultProcessor.java:228)
~[spring-data-commons-2.2.1.RELEASE.jar:2.2.1.RELEASE] at
org.springframework.data.repository.query.ResultProcessor.processResult(ResultProcessor.java:170)
~[spring-data-commons-2.2.1.RELEASE.jar:2.2.1.RELEASE] at
org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:157)
~[spring-data-jpa-2.2.1.RELEASE.jar:2.2.1.RELEASE]
SOLVED
I managed to solve my problem, using the #Query annotation, and building an interface for the response I was going to receive, in these cases with 2 methods (according to the number of parameters that I will receive), and With this I got my answer in a Json, I leave the interface code below:
public interface RespuestaVo {
String getCode();
String getResult();
}
I recommend using #Query to run Stored Procedue with Spring Boot
Try this -
#GetMapping("/service/{movil}")
public RespuestaVo ConsultarMovil(#PathVariable("movil") String movil) {
return dao.validarClienteBonifiado(movil);
}
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.
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)
I make a REST webservice with SpringBoot web and the Morphia DAO (to use MongoDB).
As I did when I used Hibernate on MySQL, I would like to use Generic entities, repositories and endpoints so that I just have to set my entities, inherit Repositories and Services and let use the generated CRUD with REST calls.
It almost done, but I encounter a problem with the generic update of my entities with Morphia. Everything I've seen so far talks about manually setting a request with the fields that have to change ; but in the Hibernate way, we just setted the Id field, called persist() and it automatically knowed what changed and applied changes in database.
Here is some code.
BaseEntity.java
package org.beep.server.entity;
import org.mongodb.morphia.annotations.Entity;
#Entity
abstract public class BaseEntity {
public static class JsonViewContext {
public interface Summary {}
public interface Detailed extends Summary{}
}
protected String id;
public void setId(String id) {
this.id = id;
}
}
User.java (one of my final entities)
package org.beep.server.entity;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.*;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import org.mongodb.morphia.annotations.*;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.ws.rs.FormParam;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
#Entity
#Indexes(
#Index(value="identifier", fields=#Field("email"))
)
#Builder
#NoArgsConstructor
#AllArgsConstructor
final public class User extends BaseEntity {
/**
* User's id
*/
#Id
#JsonView(JsonViewContext.Summary.class)
private String id;
/**
* User's email address
*/
#Getter #Setter
#JsonView(JsonViewContext.Summary.class)
#FormParam("email")
#Indexed
#Email
private String email;
/**
* User's hashed password
*/
#Getter
#JsonView(JsonViewContext.Detailed.class)
#FormParam("password")
#NotEmpty
private String password;
/**
* Sets the password after having hashed it
* #param clearPassword The clear password
*/
public void setPassword(String clearPassword) throws NoSuchAlgorithmException, InvalidKeySpecException {
PasswordEncoder encoder = new BCryptPasswordEncoder();
String hashedPassword = encoder.encode(clearPassword);
setHashedPassword(hashedPassword);
}
/**
* Directly sets the hashed password, whithout hashing it
* #param hashedPassword The hashed password
*/
protected void setHashedPassword(String hashedPassword) {
this.password = hashedPassword;
}
/**
* Converts the user to a UserDetail spring instance
*/
public UserDetails toUserDetails() {
return new org.springframework.security.core.userdetails.User(
getEmail(),
getPassword(),
true,
true,
true,
true,
AuthorityUtils.createAuthorityList("USER")
);
}
}
EntityRepository.java (my base repository, inheriting from the Morphia one)
package org.beep.server.repository;
import org.beep.server.entity.BaseEntity;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.dao.BasicDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
public class EntityRepository<Entity extends BaseEntity> extends BasicDAO<Entity, ObjectId> {
#Autowired
protected EntityRepository(Datastore ds) {
super(ds);
}
}
UserRepository.java (my user repository)
package org.beep.server.repository;
import org.beep.server.entity.User;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.dao.BasicDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
#Repository
public class UserRepository extends EntityRepository<User> {
#Autowired
protected UserRepository(Datastore ds) {
super(ds);
}
}
EntityService.java (the generic service, used from the Rest endpoints)
package org.beep.server.service;
import org.beep.server.entity.BaseEntity;
import org.beep.server.exception.EntityNotFoundException;
import org.beep.server.exception.UserEmailAlreadyExistsException;
import org.beep.server.repository.EntityRepository;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.query.Query;
import org.mongodb.morphia.query.UpdateOperations;
import java.util.List;
public abstract class EntityService<Entity extends BaseEntity, Repository extends EntityRepository> implements ServiceInterface<Entity> {
protected Repository repository;
public EntityService(Repository repository) {
this.repository = repository;
}
/**
* {#inheritDoc}
*/
public Entity create(Entity entity) throws UserEmailAlreadyExistsException {
repository.save(entity);
return entity;
}
/**
* {#inheritDoc}
*/
public void delete(String id) throws EntityNotFoundException {
//repository.deleteById(id).;
}
/**
* {#inheritDoc}
*/
public List<Entity> findAll() {
return repository.find().asList();
}
/**
* {#inheritDoc}
*/
public Entity findOneById(String id) throws EntityNotFoundException {
return (Entity) repository.get(id);
}
/**
* {#inheritDoc}
*/
public Entity update(String id, Entity entity) {
// Try to get the old entity, and to set the Id on the inputed one
// But how can I merge the two ? If I persist like that, I will just have the modified fields, others
// will be set to null...
Entity oldEntity = (Entity) repository.get(id);
entity.setId(id);
repository.save(entity);
// Create update operations works, but I have to set the changing fields manually...
// not so compatible with generics !
/*final Query<Entity> updateSelection = repository.createQuery().filter("_id",id);
repository.createUpdateOperations().
repository.update(updateSelection,entity);*/
return entity;
}
}
UserService.java
package org.beep.server.service;
import org.beep.server.entity.Message;
import org.beep.server.entity.User;
import org.beep.server.exception.EntityNotFoundException;
import org.beep.server.exception.UserEmailAlreadyExistsException;
import org.beep.server.repository.UserRepository;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Key;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.ws.rs.BadRequestException;
import java.util.List;
import java.util.Optional;
#Service
public class UserService extends EntityService<User, UserRepository> {
#Autowired
public UserService(UserRepository repository) {
super(repository);
}
}
RestResource.java (my base Rest Endpoint)
package org.beep.server.api.rest.v1;
import com.fasterxml.jackson.annotation.JsonView;
import org.beep.server.entity.BaseEntity;
import org.beep.server.entity.User;
import org.beep.server.entity.BaseEntity;
import org.beep.server.exception.EntityNotFoundException;
import org.beep.server.exception.UserEmailAlreadyExistsException;
import org.beep.server.service.EntityService;
import org.beep.server.service.ServiceInterface;
import org.beep.server.service.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
public class RestResource<Entity extends BaseEntity, Service extends EntityService> {
protected Service service;
// Default constructor private to avoid blank constructor
protected RestResource() {
this.service = null;
}
/**
* Creates an object
* #param object Object to create
* #return The newly created object
*/
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
#JsonView(BaseEntity.JsonViewContext.Detailed.class)
Entity create(#RequestBody #Valid Entity object) throws UserEmailAlreadyExistsException {
return service.create(object);
}
/**
* Deletes an object from its id
* #param id Object to delete id
* #return The deleted object
* #throws EntityNotFoundException
*/
#RequestMapping(value = "{id}", method = RequestMethod.DELETE)
#JsonView(BaseEntity.JsonViewContext.Detailed.class)
User delete(#PathVariable("id") String id) throws EntityNotFoundException {
service.delete(id);
return new User();
}
/**
* Gets all the objects
* #return All the objects
*/
#RequestMapping(method = RequestMethod.GET)
#JsonView(BaseEntity.JsonViewContext.Summary.class)
List<Entity> findAll() {
return service.findAll();
}
/**
* Finds one object from its id
* #param id The object to find id
* #return The corresponding object
* #throws EntityNotFoundException
*/
#RequestMapping(value = "{id}", method = RequestMethod.GET)
#JsonView(BaseEntity.JsonViewContext.Detailed.class)
Entity findById(#PathVariable("id") String id) throws EntityNotFoundException {
return service.findOneById(id);
}
/**
* Updates an object
* #param object The object to update
* #return The updated object
*/
#RequestMapping(value = "{id}", method = RequestMethod.PUT)
#JsonView(BaseEntity.JsonViewContext.Detailed.class)
Entity update(#PathVariable String id, #RequestBody #Valid Entity object) {
return service.update(id, object);
}
/**
* Handles the EntityNotFound exception to return a pretty 404 error
* #param ex The concerned exception
*/
#ExceptionHandler
#ResponseStatus(HttpStatus.NOT_FOUND)
public void handleEntityNotFound(EntityNotFoundException ex) {
}
/**
* Handles the REST input validation exceptions to return a pretty 400 bad request error
* with more info
* #param ex The validation exception
* #return A pretty list of the errors in the form
*/
#ExceptionHandler
#ResponseStatus(HttpStatus.BAD_REQUEST)
public List<ObjectError> handleValidationFailed(MethodArgumentNotValidException ex) {
// TODO : Check and improve the return of this method according to the front
// The concept is to automatically bind the error dans the failed parameter
return ex.getBindingResult().getAllErrors();
}
}
You've run into one of the difficult questions with Morphia. Based on the code you posted above you should look into the merge method here.
The important thing to remember is that this is not a deep merge, only top level fields, if you have complicated data objects this probably won't help.
It essentially works like this:
T Entity -> Map and then takes the map and runs a recursive update over the non-null fields like so: update({_id:#Id-field},{$set:mapOfEntityFields})
The standard transformation rules from T Entity apply ->
Map, like it does for save.
For a deep merging of any generic entity it would require you to handle this yourself with a custom method.
This is a good example from another question on SO dealing with deep merging using JSON partials in Spring over complex entities using the org.codehaus.jackson.map.ObjectMapper. It should be easily adapted to your problem.
If neither of these help you, please comment in my answer and we can work out a custom recursive method that should work for you. Hope that helps.