Imagine that we have some service:
public class SomeService
{
public void someWorkByEntity(Entity entity)
{
int id = entity.getId();
// here is some work...
}
public void someWorkByEntityId(int id)
{
// here is some work...
}
}
In fact Service require only entity id.
But write two versions of each method it's too tedious.
So what is better: Service that receives a model or an id?
Related
I searched for a while but have no clue what I am missing.
I want to implement the observer pattern in Spring, to update documents in MongoDB which will create a notification in the front-end.
I implemented a Service class for the notifications (here are more than one, I show you just two of them), using #EventListener annotation
#Service
#RequiredArgsConstructor
public class NotificationService {
#EventListener ({ContextStartedEvent.class})
public void updateNotificationForIdea(String ideaId) {
//some logic inside the Service classes
}
}
#EventListener ({ContextStartedEvent.class})
public void createNotificationForNewComment(String ideaId, String creatorId) {
//some logic inside the Service classes
}
}
and I tried to implement the service, from where I want to send the notification
#Service
#RequiredArgsConstructor
public class CommentService {
private final ApplicationEventPublisher eventPublisher;
public CommentServiceResult createComment(Comment comment, String creatorId) {
// some logic
eventPublisher.publishEvent(); //here is where I miss something
return CommentServiceResult.builder()
.comment(createdComment)
.resultState(state)
.build();
}
I read that I need something like an "EventHelperClass" and tried to build this:
public class NotificationEvents extends ApplicationEvent{
public NotificationEvents(Object source) {
super(source);
}
private void updateNotificationForIdea(String ideaId){
}
private void createNotificationForNewComment(String ideaId, String creatorId) {
}
}
but I really don't know how to get the notifications from the NotificationsService in here (because they are void and are not a simple String message), to start the event from within CommentService...
I shortened the dependencies from the services and removed the internal logik for readability...
eventPublisher.publishEvent(...)
fires an event, you'll need to pass an event object as parameter, e.g.
public class CustomSpringEvent extends ApplicationEvent {
private String message;
public CustomSpringEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
Methods annotated with #EventListener ({CustomSpringEvent.class}) will catch it.
In your case:
CommentService fires an CommentEvent (fields: Comment comment, String creatorId).
Any Bean with a method
#EventListener ({CommentEvent.class})
void onCommentEvent(CommentEvent event) {
// ....
}
will catch it and get the event data.
https://www.baeldung.com/spring-events
Designing a new application, I have two sets of domain objects. One set mirrors the other and domain objects basically are paired up with similar attributes. When a domain object in the set A is created, updated, or deleted, a corresponding domain object in the set B will also be created, updated, or deleted. To reduce any coupling, I would like to separate those operations between a pair of domain objects. What will be a good mechanism to achieve this approach? I am thinking of using a messaging system. Will it work well for this case? I use Spring for this project.
Yes, using application events is a common solution for decreasing coupling between objects.
Actually spring already has builtin mechanism for that.
You might come up with something like:
#SpringBootApplication
public class So44490189Application {
public static void main(String[] args) {
SpringApplication.run(So44490189Application.class, args);
}
public static class UserCreatedEvent {
private final String email;
public UserCreatedEvent(String email) {
this.email = email;
}
}
#Service
public static class UserService {
#EventListener
public void handleUserCreatedEvent(UserCreatedEvent userCreatedEvent) {
System.out.println("Creating user " + userCreatedEvent.email);
}
}
#Service
public static class MemberService {
#EventListener
public void handleUserCreatedEvent(UserCreatedEvent userCreatedEvent) {
System.out.println("Creating member " + userCreatedEvent.email);
}
}
#Service
public static class OperationService {
private final ApplicationEventPublisher eventPublisher;
#Autowired
public OperationService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void createUser(String email) {
eventPublisher.publishEvent(new UserCreatedEvent(email));
}
}
#RestController
public static class OperationController {
private final OperationService operationService;
#Autowired
public OperationController(OperationService operationService) {
this.operationService = operationService;
}
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public void createUser(String email) {
operationService.createUser(email);
}
}
}
Usage
curl -XPOST 'localhost:8080?email=admin#localhost.localdomain'
Output
Creating member admin#localhost.localdomain
Creating user admin#localhost.localdomain
In this case creation user and members are mirrored.
One possible problem is transaction support and there is a couple of ways to deal with that. Spring has tools for it as well.
I have a project that has been running on GWT 2.4 for some time (and 2.0 etc before that). When I switch it to GWT 2.5 or 2.6, child objects attached to my main Entity no longer save changes made to them. I don't change any code, but switching between 2.4 and 2.6, it breaks. I believe the changes don't get sent to the server. I'm watching the POST data, and it seems incomplete, missing the changes that I see being sent when on v2.4.
Are there changes to RequestFactory from 2.4-2.5 that would cause this? Something in the way I built it out that was not proper design? I appreciate any feedback!
Here's a sample retrieve/update pattern:
// Retrieve object from server
MyEntityRequest request = App.getRequestFactory().myEntityRequest();
MyEntityProxy myEntity;
request.get(id).with("child").fire(new Receiver<MyEntityProxy>() {
#Override
public void onSuccess(MyEntityProxy response) {
request = App.getRequestFactory().myEntityRequest();
myEntity = response;
}
});
// Edits made client side..
// Save updated object
myEntity = request.edit(myEntity);
myEntity.childEntity.setName("new value");
request.save(myEntity).fire(new Receiver<MyEntityProxy>() {
#Override
public void onSuccess(Void) { }
});
Example Request interface:
#Service(value = MyEntityDao.class, locator = DaoLocator.class)
public interface MyEntityRequest extends RequestContext {
Request<Void> save(MyEntityProxy entity);
}
Domain objects:
#ProxyFor(MyEntity.class, locator=DomainObjectLocator.class)
public interface MyEntityProxy extends EntityProxy {
Integer getId();
Set<ChildProxy> getChildren();
void setChildren(Set<ChildProxy> children);
}
#ProxyFor(Child.class, locator=DomainObjectLocator.class)
public interface ChildProxy extends EntityProxy {
Integer getId();
String getName();
void setName(String name);
}
Server object:
#Entity
#Table (name="MyEntity")
#BatchSize(size=25)
public class MyEntity extends DomainObject {
#OneToMany (mappedBy = "myEntity")
#BatchSize(size=25)
#Cascade(CascadeType.ALL)
private Set<Child> children;
}
Server persist:
public static Void save(MyEntity myEntity) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
try {
myEntity = (MyEntity) session.merge(myEntity);
} catch (Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally {
session.getTransaction().commit();
}
}
It probably has something to do with https://code.google.com/p/google-web-toolkit/issues/detail?id=7827
TL;DR: yes, there were some changes in 2.5.
In my Spring web app I'm using a generic dao class:
public abstract class GenericDaoImpl<T> implements GenericDao<T> {
#Override
public T create(final T t) {
this.getEntityManager().persist(t);
return t;
}
#Override
public void delete(final Object id) {
this.getEntityManager().remove(
this.getEntityManager().getReference(getEntityType(), id));
}
#Override
public T find(final Object id) {
return (T) this.getEntityManager().find(getEntityType(), id);
}
#Override
public T update(final T t) {
return this.getEntityManager().merge(t);
}
}
I implement this class for every entity in my model and it works perfectly. For example:
#Repository
public class GruppoDaoImpl extends GenericDaoImpl<Gruppo> implements GruppoDao {
}
I use these dao classes in my service layer. I have a service layer for every entity in my model, but methods for most of these classes, are the same, so I tried to create a generic service class that I can extend in the same way I do for the generic dao:
public abstract class GenericAdminServiceImpl<ENTITY extends AbstractEntity, DTO extends AbstractDto>
implements GenericAdminService<ENTITY, DTO> {
private GenericDao<ENTITY> dao;
private Class<ENTITY> entityClass;
private Class<DTO> dtoClass;
#SuppressWarnings({ "unchecked", "rawtypes" })
protected GenericAdminServiceImpl(GenericDao<ENTITY> dao) {
this.dao = dao;
//
Type t = getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) t;
this.entityClass = (Class) pt.getActualTypeArguments()[0];
this.dtoClass = (Class) pt.getActualTypeArguments()[1];
}
public DTO getById(Object id) {
DTO dto = null;
ENTITY entity = dao.find(id);
if (entity != null) {
try {
dto = dtoClass.newInstance();
initDto(entity, dto);
} catch (Exception e) {
}
}
return dto;
}
public void create(DTO dto) throws ServiceOperationException {
ENTITY entity;
try {
entity = entityClass.newInstance();
initEntity(dto, entity);
Date dt = new Date();
entity.setDataUltimoAggiornamento(dt);
entity.setUtenteUltimoAggiornamento(dto.getLoggedUser());
entity.setDataInserimento(dt);
entity.setUtenteInserimento(dto.getLoggedUser());
dao.create(entity);
} catch (Exception e) {
throw new ServiceOperationException("impossibile creare entity ["
+ entityClass.getSimpleName() + "]", e);
}
}
public void update(DTO dto) throws ServiceOperationException {
ENTITY entity = dao.find(dto.getId());
if (!entityExists(entity)) {
throw new ServiceOperationException("entity non esistente ["
+ entityClass.getSimpleName() + "#" + dto.getId() + "]");
}
initEntity(dto, entity);
Date dt = new Date();
entity.setDataUltimoAggiornamento(dt);
entity.setUtenteUltimoAggiornamento(dto.getLoggedUser());
dao.update(entity);
}
public void delete(Object id) throws ServiceOperationException {
try {
dao.delete((int) id);
} catch (Exception e) {
throw new ServiceOperationException(
"impossibile eliminare entity ["
+ entityClass.getSimpleName() + "#" + id + "]", e); // TODO
}
}
protected abstract void initDto(ENTITY entity, DTO outDto);
protected abstract void initEntity(DTO dto, ENTITY outEntity);
protected abstract boolean entityExists(ENTITY entity);
}
Extending this class I just have to implement specific parts for every entity, leaving all the common stuff in the abstract/generic class.
The problem is that using the generic service, merge, persist and delete don't work. Only select seems to work and I cannot understand why...
When I run debug mode in Eclipse all seems correct. A consistent entity is passed to merge/persist methods, so why they don't work? can you help me?
UPDATE #1
This is an example of implementation:
#Service
#Transactional(propagation = Propagation.REQUIRES_NEW)
public class GruppoServiceImplG extends
GenericAdminServiceImpl<Gruppo, GruppoDto> implements GruppoServiceG {
#Autowired
protected GruppoServiceImplG(GruppoDao gruppoDao) {
super(gruppoDao);
}
#Override
protected void initDto(Gruppo entity, GruppoDto outDto) {
outDto.setId(entity.getId());
outDto.setNome(entity.getNome());
outDto.setDescrizione(entity.getDescrizione());
outDto.setDataInizioValidita(entity.getDataInizioValidita());
outDto.setDataFineValidita(entity.getDataFineValidita());
}
#Override
protected void initEntity(GruppoDto dto, Gruppo outEntity) {
outEntity.setId(dto.getId());
outEntity.setNome(dto.getNome());
outEntity.setDescrizione(dto.getDescrizione());
outEntity.setDataInizioValidita(dto.getDataInizioValidita());
outEntity.setDataFineValidita(dto.getDataFineValidita());
}
#Override
protected boolean entityExists(Gruppo entity) {
return entity != null && entity.getId() > 0;
}
}
UPDATE #2
Following Łukasz L. suggestion, I added to all my crud methods a flush(). Now I get this exception javax.persistence.TransactionRequiredException: no transaction is in progress. What's wrong with my transaction declaration? it works fine with non-generic serices...
If you read that question about Spring and Hibernate flush behaviour, it's not easy that commiting your transaction will make also the EntityManager to save all changes. Spring and JPA (Hibernate&CO) are designed to work quite nice (from the Spring side) but nevertheless, you must assert that your entity manager will write all queries to database before commiting transaction.
The problem: JPAs like to cache. It means, they tend to avoid issuing queries. If you do SELECT, they have no choice - they must fetch some data (as long as that data portion was not fetched - like when getting single entity by ID). By INSERTs and UPDATEs - well, they CAN cache. It means, that create, merge or remove will usually not issue a query to RDBMS until you call flush() on EntityManager.
If you leave transactional block without calling flush, and entity manager is delaying operations, you'll commit transactions, by which the modifying queries were not issued!
Just make sure to call EntityManager.flush() at least at the end of the transactional method. You can also call it after each DML operation, it's your choice (I prefer that way because it gives me full control in which order the DML queries are issued by JPA, if you heavily uses DB constraints/triggers, it can be essential).
#Transactional
public void myTransactionalMethod() {
getEntityManager().persist(a); // I've made some JPA change, that is not issued to DB yet
...
// I'm doing something more
...
getEntityManager().flush(); // the last moment to flush, after that instruction I leave transactional context
}
Following Łukasz L. suggestion I discovered the actual issue in my generic class.
Transaction declaration was wrong. I set #Transactional(propagation = Propagation.REQUIRES_NEW) only in concrete service class.
I solved this way:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public abstract class GenericAdminServiceImpl<ENTITY extends AbstractEntity, DTO extends AbstractDto>
implements GenericAdminService<ENTITY, DTO> {
// ...
}
And (in concrete implementation):
#Service
#Transactional
public class GruppoServiceImplG extends
GenericAdminServiceImpl<Gruppo, GruppoDto> implements GruppoServiceG {
// ...
}
I'm usnig Google App Engine with Java, and using Datastore via JPA.
I want to save profile image for each user:
#Entity
public class User implements Serializable {
#Id
private String userId;
private String imageKey; // saves BlobKey.getKeyString()
// ... other fields and getter/setter methods ...
}
If user wants to change their profile image, I (should) update imageKey field AND delete old image associated with old imageKey.
But, the document of Blobstore for Python says:
Deleting a Blobstore value occurs separately from datastore transactions. If you call this method during a datastore transaction, it takes effect immediately, regardless of whether the transaction commits successfully or is retried.
This seems that I can't make updating imageKey and deleting old image as one atomic action, and it also would affect to Java.
This is my attempt to do this work:
public class Engine implements ServletContextListener {
private EntityManagerFactory emf;
private BlobstoreService blobstoreService;
// Servlet will call getUser, modify returned object, and call updateUser with that object
public User getUser(final String userId) throws EngineException {
return doTransaction(new EngineFunc<User>() { // Utility method for create Entity manager and start transaction
#Override
public User run(EntityManager em) throws EngineException {
return em.find(User.class, key);
}
});
}
public void updateUser(final User user) throws EngineException {
doTransaction(new EngineFunc<Void>() {
#Override
public Void run(EntityManager em) throws EngineException {
em.merge(user);
return null;
}
});
user.purgeOldImages(blobstoreService);
}
// ... Other methods ...
}
public class User {
#Transient
private transient Set<String> oldImageList = new CopyOnWriteArraySet<>();
public void setImageKey(String imageKey) {
if (this.imageKey != null) {
oldImageList.add(this.imageKey);
}
this.imageKey = imageKey;
if (imageKey != null) {
oldImageList.remove(imageKey);
}
}
public void purgeOldImages(BlobstoreService blobService) {
Set<BlobKey> toDelete = new HashSet<>();
for (String s : oldImageList) {
toDelete.add(new BlobKey(s));
oldImageList.remove(s);
}
blobService.delete(toDelete.toArray(new BlobKey[0]));
}
// ... Other methods ...
}
I think this is neither "beautiful" nor correct code.
Is there the right way to do this?