Question

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...

Was it helpful?

Solution 2

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 {

    // ...

}

OTHER TIPS

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
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top