Dao Genérico, Transações diferentes e Rollback

Olá

Hoje tenho um dao generico dessa forma

abstract class GenericDAO<T> implements Serializable {

    public GenericDAO(Class<T> entityClass) { 
        this.entityClass = entityClass; 
    }

    public Session getSession() {
		if (session == null || !session.isOpen()) {
			session = sessionFactory.openSession();
		}
		return session;
	}
}

Dessa forma eu preciso criar um dao específico para cada entidade do sistema.

public class TesteDAO extends GenericDAO<Teste> { 
  
    public TesteDAO() { 
        super(Teste.class); 
    } 
  
}

Isso está funcionando, mas eu me peguei em uma situação ruim.
Tem um momento em q eu tenho 3 entidades relacionadas e preciso salvar as 3.
Se eu salvar duas delas e der problema na terceira, ele mantém as duas salvas.
Eu preciso fazer rollback nas duas salvas, mas isso não está tão fácil assim.

Não ta facil pq eu faço o seguinte: salvo uma entidade e commito para poder pegar a referencia e relacionar na outra entidade.
Se eu não commitar ele dá erro na hora de relacionar a entidade salva (e não commitada) na que não foi salva ainda.
Isso pq (acredito eu) estão em EntityManager e transações diferentes e uma não enxerga a outra.

Poderiam me sugerir uma espécie de Dao genérico que não caia nesse problema ?
Ou uma forma para resolver esse problema neste tipo de Dao.

Obrigado

Mas você não precisa fazer essa barbaridade toda.
Você deve confirmar a transação (commit) apenas ao fim da execução de todas as alterações no banco.
Quando você faz:

session.save(obj);

A referência permanece e, dependendo de como você gerencia a PK no banco, ele já recebe a PK e está pronto para uso. O próprio hibernate faz isso por você (já que usa Session).

Mas é isso q eu qria e oq fui fazer, mas não rolou.

Com o dao dessa forma eu preciso fazer o seguinte.

EntidadeUmDao entidadeUmDao = new EntidadeUmDao();
entidadeUmDao.beginTransaction();
entidadeUmDao.save(entidadeUm);
entidadeUmDao.commit();

Se eu não fizer o commit, dá erro qd eu faço isso:
EntidadeDoisDao entidadeDoisDao = new EntidadeDoisDao();
entidadeDois.setEntidadeUm(entidadeUm);
entidadeDoisDao.save(entidadeDois);

Exception in thread "main" org.hibernate.exception.GenericJDBCException: could not insert: [Teste]
	at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:103)
	at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:91)
	at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
	at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:40)
	at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2158)
	at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2638)
	at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:48)
	at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
	at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:298)
	at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:181)
	at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:107)
	at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:187)
	at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:172)
	at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:94)
	at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
	at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:507)
	at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:499)
	at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:495)
	at GenericDAO.save(GenericDAO.java:76)
	at TesteDAO.save(TesteDAO.java:1)
	at JpaMain.main(JpaMain.java:31)
Caused by: java.sql.SQLException: Got error -1 from storage engine
	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1075)
	at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3566)
	at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3498)
	at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1959)
	at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2113)
	at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2568)
	at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2113)
	at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2409)
	at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2327)
	at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2312)
	at org.hibernate.id.IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(IdentityGenerator.java:73)
	at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:33)
	... 17 more

Você provavelmente está fazendo lambança com isso.
Como estão mapeadas as entidades relacionadas?

Pode ser, porém eu fiz um exemplo bem simples aqui.

@Entity
@Table(name="EntidadeUm")
public class EntidadeUm implements Serializable {

	@Id @GeneratedValue
	private Long id;
	
	private String nome;
	
	@OneToOne
	@JoinColumn(name = "entidadeDois_id")
	private EntidadeDois entidadeDois;
	
}
@Entity
@Table(name="EntidadeDois")
public class EntidadeDois implements Serializable {

	@Id @GeneratedValue
	private Long id;
	
	@Column(nullable = false)
	private String nome;
}

Desta forma que dá o erro:

public static void main(String[] args) {
		
		EntidadeDois entidadeDois = new EntidadeDois();
		entidadeDois.setNome("EntidadeDois");
		
		//DAO
		EntidadeDoisDAO entidadeDoisDAO = new EntidadeDoisDAO();
		entidadeDoisDAO.beginTransaction();
		//SAVE
		entidadeDoisDAO.save(entidadeDois);
		
		EntidadeUm entidadeUm = new EntidadeUm();
		entidadeUm.setNome("EntidadeUm");
		entidadeUm.setEntidadeDois(entidadeDois);
		
		//DAO
		EntidadeUmDAO entidadeUmDAO = new EntidadeUmDAO();
		entidadeUmDAO.beginTransaction();
		//SAVE
		entidadeUmDAO.save(entidadeUm);
		
		//COMMITS
		entidadeDoisDAO.commit();
		entidadeUmDAO.commit();
		
	}

Mas se eu fizer o commit() logo após o save() não dá erro algum e tudo é salvo de boa.
Acredito q esse erro aconteça pelo fato de serem transações diferentes e um não enxerga o outro já que não teve o commit.

entenda como funciona UserTransaction e ContainerTransaction e analise qual delas se adequa mais ao seu cenário.

Quando você invoca

entidadeDoisDAO.beginTransaction();

E depois

entidadeUmDAO.beginTransaction();  

Você está criando duas transações diferentes. Isso não deve ocorrer. As referências irão existir dentro da mesma transação, até que um commit seja executado.
Você tem um DAO genérico, por que não faz uso dele?
Faça o DAO genérico gerenciar as transações.

Ele obviamente não está trabalhando com um app server…

Ele obviamente não está trabalhando com um app server…[/quote]

jta independe de app server. ja usei UserTransaction em sistema desktop.

Ele obviamente não está trabalhando com um app server…[/quote]

jta independe de app server. ja usei UserTransaction em sistema desktop.[/quote]
Mas desktop, por padrão, é UserTransaction…

[quote=drsmachado]
Mas desktop, por padrão, é UserTransaction…[/quote]

ah tá, então não precisa implementar no DAO ?

Então, é oq eu falei das duas transações.
Eu uso o GenericDao, porém eu faço isso devido a forma do GenericDao.

abstract class GenericDAO<T> implements Serializable {

	private static SessionFactory sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
	
	private Session session;
	
	private Class<T> entityClass; 
	
	public GenericDAO(Class<T> entityClass) { 
            this.entityClass = entityClass; 
        }

public Session getSession() {
		if (session == null || !session.isOpen()) {
			session = sessionFactory.openSession();
		}
		return session;
	}

    //demais metodos

}
public class EntidadeUmDAO extends GenericDAO<EntidadeUm> { 
  
    public EntidadeUmDAO() { 
        super(EntidadeUm.class); 
    } 
  
}
public class EntidadeDoisDAO extends GenericDAO<EntidadeDois> { 
  
    public EntidadeDoisDAO() { 
        super(EntidadeDois.class); 
    } 
  
}

Pelo q estou vendo preciso deixar o GenericDao mais genérico.

Vi q oq preciso fazer é para resolver isso é um joinTransaction(), mas ainda não achei como fazer isso no hibernate.
Com JPA é fácil, mas eu uso Session do hibernate.

public void joinTransaction() {
        em = emf.createEntityManager();
        em.joinTransaction();
    }