Estou com uma dúvida sobre como as transações funcionam no EJB.
Para melhor a compreensão estou apresentando abaixo as classes que criei para realizar meus testes.
O que estou tentando testar é como utilizar a mesma transação para dois métodos de dois session beans diferentes, para que se ocorrer um erro em um deles o outro, caso já tenha sido executado, esteja incluído no rollback da transação. Esse teste pode ser observado no método gravar da classe UsuarioBusiness.
Eu forcei um erro na atualização da entidade usuário setando o campo cpf como nulo e observando se o insert de usuarioHist seria incluido no rollback. Eu cheguei até a mudar o TransactionManagement de CONTAINER para BEAN (controlando eu mesmo a transação), o que não ajudou em nada.
O que eu gostaria de saber é: Como eu faço para se ocorrer algum erro na atualização dos dados do usuario o registro de usuarioHist gravado seja desfeito.
O banco de dados que estou utilizando é o MySQL com tabelas criadas com a engine INNODB.
UsuarioDAO.java
@TransactionManagement(TransactionManagementType.CONTAINER)
@Stateless(name = "usuarioDAO")
@LocalBean
public class UsuarioDAO {
/**
* Gerenciador de entidades
*/
@PersistenceContext(unitName = "experimento_pu")
private EntityManager entityManager;
/**
* Default constructor.
*/
public UsuarioDAO() {
}
/**
* @param usuario
*/
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void excluir(final Usuario usuario) {
this.entityManager.remove(this.entityManager.find(Usuario.class, usuario.getId()));
}
/**
* @param usuario
* @return Usuario que foi persistido no banco
* @throws Exception
*/
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Usuario gravar(final Usuario usuario) throws Exception {
this.entityManager.joinTransaction();
Usuario temp = this.entityManager.merge(usuario);
return temp;
}
/**
* @return lista de usuarios ordenada pelos seus respsctivos nomes
*/
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public List<Usuario> listarTodos() {
TypedQuery<Usuario> query = this.entityManager.createQuery("SELECT u FROM Usuario u ORDER BY u.nome", Usuario.class);
return query.getResultList();
}
/**
* @param first
* @param pageSize
* @return lista de usuarios paginada e ordenada pelos seus respsctivos
* nomes
*/
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public List<Usuario> listarTodosPaginado(int first, int pageSize) {
TypedQuery<Usuario> query = this.entityManager.createQuery("SELECT u FROM Usuario u ORDER BY u.nome", Usuario.class);
query.setFirstResult(first);
query.setMaxResults(pageSize);
return query.getResultList();
}
/**
* @param usuario
*/
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void persistir(final Usuario usuario) {
this.entityManager.persist(usuario);
}
/**
* @param id
* @return Usuario que tem o identificador informado
*/
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public Usuario pesquisarPorId(final Long id) {
return this.entityManager.find(Usuario.class, id);
}
/**
* @return quantidade total de registros
*/
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public Long qtdListarTodosPaginado() {
TypedQuery<Long> query = this.entityManager.createQuery("SELECT COUNT(u) FROM Usuario u", Long.class);
return query.getSingleResult();
}
}
UsuarioHistDAO.java
@TransactionManagement(TransactionManagementType.CONTAINER)
@Stateless(name = "usuarioHistDAO")
@LocalBean
public class UsuarioHistDAO{
/**
* Gerenciador de entidades
*/
@PersistenceContext(unitName = "experimento_pu")
private EntityManager entityManager;
/**
* Default constructor.
*/
public UsuarioHistDAO() {
}
/**
* @param usuarioHist
* @return Historico do usuario que foi persistido no banco
* @throws Exception
*/
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public UsuarioHist gravar(final UsuarioHist usuarioHist) throws Exception {
this.entityManager.joinTransaction();
UsuarioHist temp = this.entityManager.merge(usuarioHist);
return temp;
}
/**
* @param usuarioHist Historico do usuario
*/
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void excluir(final UsuarioHist usuarioHist) {
this.entityManager.remove(this.entityManager.find(UsuarioHist.class, usuarioHist.getId()));
}
/**
* @return Lista de historico do usuarios ordenada pelos seus respsctivos ids
*/
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public List<UsuarioHist> listarTodos() {
TypedQuery<UsuarioHist> query = this.entityManager.createQuery("SELECT uh FROM UsuarioHist uh ORDER BY uh.id", UsuarioHist.class);
return query.getResultList();
}
/**
* @param usuarioHist Historico do usuario
*/
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void persistir(final UsuarioHist usuarioHist) {
this.entityManager.persist(usuarioHist);
}
/**
* @param id
* @return Historico do usuario que tem o identificador informado
*/
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public UsuarioHist pesquisarPorId(final Long id) {
return this.entityManager.find(UsuarioHist.class, id);
}
}
UsuarioBusiness.java
@TransactionManagement(TransactionManagementType.CONTAINER)
@Stateless(name = "usuarioBusiness")
public class UsuarioBusiness implements
UsuarioBusinessLocal {
/**
*
*/
@EJB
private UsuarioDAO usuarioDAO;
/**
*
*/
@EJB
private UsuarioHistDAO usuarioHistDAO;
/**
* Default constructor.
*/
public UsuarioBusiness() {
}
/**
* @param usuario
*/
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void excluir(final Usuario usuario) {
this.usuarioDAO.excluir(usuario);
}
/**
* @param usuario
* @return Usuario que foi persistido no banco
*/
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Usuario gravar( final Usuario usuario,
final Usuario autenticado) {
try {
if (usuario.getId() != null) {
usuario.setCpf(null);
this.usuarioHistDAO.gravar(new UsuarioHist( this.usuarioDAO.pesquisarPorId(usuario.getId()),
autenticado));
} else {
usuario.setCriadoPor(autenticado);
usuario.setCriado(Calendar.getInstance().getTime());
}
return this.usuarioDAO.gravar(usuario);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* @return lista de usuarios ordenada pelos seus respsctivos nomes
*/
@Override
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public List<Usuario> listarTodos() {
return this.usuarioDAO.listarTodos();
}
/**
* @param first
* @param pageSize
* @return lista de usuarios paginada e ordenada pelos seus respsctivos
* nomes
*/
@Override
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public List<Usuario> listarTodosPaginado( int first,
int pageSize) {
return this.usuarioDAO.listarTodosPaginado( first,
pageSize);
}
/**
* @param id
* @return Usuario que tem o identificador informado
*/
@Override
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public Usuario pesquisarPorId(final Long id) {
return this.usuarioDAO.pesquisarPorId(id);
}
/**
* @return quantidade total de registros
*/
@Override
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public Long qtdListarTodosPaginado() {
return this.usuarioDAO.qtdListarTodosPaginado();
}
}
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="experimento_pu" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:jboss/datasources/TesteDS</jta-data-source>
<class>br.com.experimento.entity.Erro</class>
<class>br.com.experimento.entity.Usuario</class>
<class>br.com.experimento.entity.UsuarioHist</class>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
<property name="hibernate.show_sql" value="false" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.max_fetch_depth" value="3" />
<property name="hibernate.connection.autocommit" value="false"/>
</properties>
</persistence-unit>
</persistence>
Usuario.java
@Entity
@Table(name = "usuario")
public class Usuario implements
Serializable {
/**
*
*/
private static final long serialVersionUID = -5957682803614915740L;
/**
*
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "USU_ID",
updatable = false,
unique = true,
nullable = false)
private Long id;
/**
*
*/
@Column(name = "USU_CPF", nullable = false, length = 11)
private String cpf;
/**
*
*/
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "USU_CRIADO",
updatable = false,
nullable = false)
private Date criado;
/**
*
*/
@Column(name = "USU_LOGIN",
nullable = false,
length = 50)
private String login;
/**
*
*/
@Column(name = "USU_NOME",
nullable = false,
length = 100)
private String nome;
/**
*
*/
@Column(name = "USU_SENHA",
nullable = false,
length = 100)
private String senha;
/**
*
*/
@Column(name = "USU_STATUS", nullable = false)
private boolean status;
/**
*
*/
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "USU_ULTIMO_ACESSO")
private Date ultimoAcesso;
/**
*
*/
@Version
@Column(name = "USU_VERSAO",
updatable = false,
nullable = false)
private Long versao;
/**
* uni-directional many-to-one association to Usuario
*/
@ManyToOne
@JoinColumn(name = "USU_CRIADO_POR")
private Usuario criadoPor;
/**
* bi-directional many-to-one association to UsuarioHist
*/
@OneToMany( mappedBy = "usuario",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER)
private List<UsuarioHist> historico;
/**
*
*/
public Usuario() {
}
/**
* @return CPF
*/
public String getCpf() {
return this.cpf;
}
/**
* @return Data em que o registro foi criado
*/
public Date getCriado() {
return this.criado;
}
/**
* @return Usuario que criou o registro
*/
public Usuario getCriadoPor() {
return this.criadoPor;
}
/**
* @return Historico do registro
*/
public List<UsuarioHist> getHistorico() {
return this.historico;
}
/**
* @return Identificador do usuario
*/
public Long getId() {
return this.id;
}
/**
* @return Utilizado para identificar um usuario no sistema
*/
public String getLogin() {
return this.login;
}
/**
* @return Nome do usuario
*/
public String getNome() {
return this.nome;
}
/**
* @return Nome do usuario responsavel pela criacao do registro
*/
public String getNomeCriador() {
if (this.criadoPor != null
&& this.criadoPor.getNome() != null
&& !this.criadoPor.getNome().isEmpty()) {
return this.criadoPor.getNome();
} else {
return "N/A";
}
}
/**
* @return Data da ultima alteracao realizada no registro
*/
public Date getDataUltimaAlteracao() {
if (this.historico != null
&& !this.historico.isEmpty()) {
return this.historico.get(this.historico.size() - 1).getAlterado();
} else {
return null;
}
}
/**
* @return Nome do usuario responsavel pela ultima alteracao realizada no
* registro
*/
public String getUsuarioUltimaAlteracao() {
if (this.historico != null
&& !this.historico.isEmpty()) {
return this.historico.get(this.historico.size() - 1).getNomeAlterador();
} else {
return "N/A";
}
}
/**
* @return Utilizado para autenticar um usuario no sistema
*/
public String getSenha() {
return this.senha;
}
/**
* @return Data da ultima autenticacao do usuario no sistema
*/
public Date getUltimoAcesso() {
return this.ultimoAcesso;
}
/**
* @return versao do registro
*/
public Long getVersao() {
return this.versao;
}
/**
* @param cpf
*/
public void setCpf(String cpf) {
this.cpf = cpf;
}
/**
* @param criado
*/
public void setCriado(Date criado) {
this.criado = criado;
}
/**
* @param criadoPor
*/
public void setCriadoPor(Usuario criadoPor) {
this.criadoPor = criadoPor;
}
/**
* @param historico
*/
public void setHistorico(List<UsuarioHist> historico) {
this.historico = historico;
}
/**
* @param id
*/
public void setId(Long id) {
this.id = id;
}
/**
* @param login
*/
public void setLogin(String login) {
this.login = login;
}
/**
* @param nome
*/
public void setNome(String nome) {
this.nome = nome;
}
/**
* @param senha
*/
public void setSenha(String senha) {
this.senha = senha;
}
/**
* @param ultimoAcesso
*/
public void setUltimoAcesso(Date ultimoAcesso) {
this.ultimoAcesso = ultimoAcesso;
}
/**
* @param versao
*/
public void setVersao(Long versao) {
this.versao = versao;
}
/**
* @return Status do registro true = ativo, false = inativo
*/
public boolean isStatus() {
return this.status;
}
/**
* @param status
*/
public void setStatus(final boolean status) {
this.status = status;
}
/**
* @return Status do usuario, ativo ou inativo
*/
public String getStatusFormatado() {
if (this.status) {
return "Ativo";
} else {
return "Inativo";
}
}
}
UsuarioHist.java
@Entity
@Table(name = "usuario_hist")
public class UsuarioHist implements
Serializable {
/**
*
*/
private static final long serialVersionUID = -5589225593308150574L;
/**
*
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "USH_ID", updatable = false, unique = true, nullable = false)
private Long id;
/**
*
*/
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "USU_ALTERADO", updatable = false, nullable = false)
private Date alterado;
/**
*
*/
@Column(name = "USU_CPF", updatable = false, nullable = false, length = 11)
private String cpf;
/**
*
*/
@Column(name = "USU_LOGIN", updatable = false, nullable = false, length = 50)
private String login;
/**
*
*/
@Column(name = "USU_NOME", updatable = false, nullable = false, length = 100)
private String nome;
/**
*
*/
@Column(name = "USU_SENHA", updatable = false, nullable = false, length = 100)
private String senha;
/**
*
*/
@Column(name = "USU_STATUS", updatable = false, nullable = false)
private boolean status;
/**
*
*/
@Column(name = "USU_VERSAO", updatable = false, nullable = false)
private Long versao;
// bi-directional many-to-one association to Usuario
/**
*
*/
@ManyToOne
@JoinColumn(name = "USH_USU_ID", nullable = false)
private Usuario usuario;
// uni-directional many-to-one association to Usuario
/**
*
*/
@ManyToOne
@JoinColumn(name = "USU_ALTERADO_POR", nullable = false)
private Usuario alteradoPor;
/**
*
*/
public UsuarioHist() {
}
/**
* @param usuario
* @param autenticado
*/
public UsuarioHist(Usuario usuario, Usuario autenticado) {
this.usuario = usuario;
this.alterado = Calendar.getInstance().getTime();
this.alteradoPor = autenticado;
this.cpf = usuario.getCpf();
this.nome = usuario.getNome();
this.login = usuario.getLogin();
this.senha = usuario.getSenha();
this.status = usuario.isStatus();
this.versao = usuario.getVersao();
}
/**
* @return Data emq ue o registro foi alterado
*/
public Date getAlterado() {
return this.alterado;
}
/**
* @return usuario responsavel pela alteracao
*/
public Usuario getAlteradoPor() {
return this.alteradoPor;
}
/**
* @return CPF
*/
public String getCpf() {
return this.cpf;
}
/**
* @return Identificador do historico
*/
public Long getId() {
return this.id;
}
/**
* @return Login
*/
public String getLogin() {
return this.login;
}
/**
* @return Nome
*/
public String getNome() {
return this.nome;
}
/**
* @return Senha
*/
public String getSenha() {
return this.senha;
}
/**
* @return Status
*/
public boolean getStatus() {
return this.status;
}
/**
* @return Usuario a quem esse historico pertence
*/
public Usuario getUsuario() {
return this.usuario;
}
/**
* @return Versao original antes da alteracao
*/
public Long getVersao() {
return this.versao;
}
/**
* @return Nome do usuario responsavel pela criacao do registro
*/
public String getNomeAlterador() {
if (this.alteradoPor != null && this.alteradoPor.getNome() != null && !this.alteradoPor.getNome().isEmpty()) {
return this.alteradoPor.getNome();
} else {
return "N/A";
}
}
/**
* @param alterado
*/
public void setAlterado(Date alterado) {
this.alterado = alterado;
}
/**
* @param alteradoPor
*/
public void setAlteradoPor(Usuario alteradoPor) {
this.alteradoPor = alteradoPor;
}
/**
* @param cpf
*/
public void setCpf(String cpf) {
this.cpf = cpf;
}
/**
* @param id
*/
public void setId(Long id) {
this.id = id;
}
/**
* @param login
*/
public void setLogin(String login) {
this.login = login;
}
/**
* @param nome
*/
public void setNome(String nome) {
this.nome = nome;
}
/**
* @param senha
*/
public void setSenha(String senha) {
this.senha = senha;
}
/**
* @param status
*/
public void setStatus(boolean status) {
this.status = status;
}
/**
* @param usuario
*/
public void setUsuario(Usuario usuario) {
this.usuario = usuario;
}
/**
* @param versao
*/
public void setVersao(Long versao) {
this.versao = versao;
}
}
Tabela usuario
+-------------------+---------------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+---------------------+------+-----+-------------------+----------------+
| USU_ID | bigint(18) unsigned | NO | PRI | NULL | auto_increment |
| USU_CPF | varchar(11) | NO | | NULL | |
| USU_NOME | varchar(100) | NO | | NULL | |
| USU_LOGIN | varchar(50) | NO | | NULL | |
| USU_SENHA | varchar(100) | NO | | NULL | |
| USU_STATUS | tinyint(1) | NO | | 1 | |
| USU_ULTIMO_ACESSO | timestamp | YES | | NULL | |
| USU_CRIADO | timestamp | NO | | CURRENT_TIMESTAMP | |
| USU_CRIADO_POR | bigint(18) unsigned | YES | MUL | NULL | |
| USU_VERSAO | bigint(18) unsigned | NO | | NULL | |
+-------------------+---------------------+------+-----+-------------------+----------------+
Tabela usuario_hist
+------------------+---------------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+---------------------+------+-----+-------------------+----------------+
| USH_ID | bigint(18) unsigned | NO | PRI | NULL | auto_increment |
| USH_USU_ID | bigint(18) unsigned | NO | MUL | NULL | |
| USU_CPF | varchar(11) | NO | | NULL | |
| USU_NOME | varchar(100) | NO | | NULL | |
| USU_LOGIN | varchar(50) | NO | | NULL | |
| USU_SENHA | varchar(100) | NO | | NULL | |
| USU_STATUS | tinyint(1) | NO | | NULL | |
| USU_VERSAO | bigint(18) unsigned | NO | | NULL | |
| USU_ALTERADO | timestamp | NO | | CURRENT_TIMESTAMP | |
| USU_ALTERADO_POR | bigint(18) unsigned | NO | MUL | NULL | |
+------------------+---------------------+------+-----+-------------------+----------------+