Pessoal,
Tenho a seguinte modelagem:
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "cad_Usuarios")
public abstract class Usuario extends MappedEntity {
private static final long serialVersionUID = -3419086166278247235L;
private User user;
private String nome;
private String email;
private Date dataCadastro;
@Override
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "NUMG_Usuario")
public Long getId() {
return super.getId();
}
@OneToOne(fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
@Cascade( { org.hibernate.annotations.CascadeType.ALL })
@JoinColumn(name = "NUMG_UsuarioFwe", nullable = false)
public User getUser() {
return user;
}
@Column(name = "DESC_Nome", length = 50)
@Length(max = 50)
public String getNome() {
return nome;
}
@Column(name = "DESC_Email", length = 80)
@Length(max = 80)
@IgnoreUpperCase
public String getEmail() {
return email;
}
@Column(name = "DATA_Cadastro")
@Temporal(TemporalType.TIMESTAMP)
public Date getDataCadastro() {
return dataCadastro;
}
public void setUser(User user) {
this.user = user;
}
public void setNome(String nome) {
this.nome = nome;
}
public void setEmail(String email) {
this.email = email;
}
public void setDataCadastro(Date dataCadastro) {
this.dataCadastro = dataCadastro;
}
@Entity
@Table(name = "fwe_Usuarios")
public class User extends MappedEntity {
public static final String DEFAULT_PASSWORD = "senha";
private static final long serialVersionUID = 1L;
private String login;
private String password;
private Set<Profile> profiles;
private Date activationBeginning;
private Integer activationDays;
@Override
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "NUMG_Usuario")
public Long getId() {
return super.getId();
}
@Basic(optional = true)
@Column(name = "NUMR_DuracaoDias")
public Integer getActivationDays() {
return activationDays;
}
public void setActivationDays(Integer duration) {
this.activationDays = duration;
}
@Temporal(TemporalType.DATE)
@Column(name = "DATA_InicioValidade")
public Date getActivationBeginning() {
return activationBeginning;
}
public void setActivationBeginning(Date date) {
this.activationBeginning = date;
}
@Column(length = 32, unique = true, nullable = false, name = "DESC_Login")
@Length(min = 5, max = 32)
@IgnoreSpecialChars
@IgnoreUpperCase
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
@Column(length = 50, nullable = false, name = "DESC_Senha")
@Length(max = 50)
@IgnoreSpecialChars
@IgnoreUpperCase
public String getPassword() {
return password;
}
public void setPassword(String senha) {
this.password = senha;
}
@ManyToMany
@JoinTable(name = "colUsuariosPerfis", joinColumns = { @JoinColumn(name = "NUMG_Usuario") }, inverseJoinColumns = { @JoinColumn(name = "NUMG_Perfil") })
public Set<Profile> getProfiles() {
if (profiles == null) {
profiles = new HashSet<Profile>();
}
return profiles;
}
public void setProfiles(Set<Profile> perfis) {
this.profiles = perfis;
}
@Transient
public boolean addProfile(Profile profile) {
return getProfiles().add(profile);
}
@Transient
public boolean removeProfile(Profile profile) {
return getProfiles().remove(profile);
}
@Override
@Transient
public String toString() {
return login;
}
hibernate.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="sessionFactory" lazy-init="true" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName">
<value>${driverClassName}</value>
</property>
<property name="url">
<value>${url}</value>
</property>
<property name="username">
<value>${username}</value>
</property>
<property name="password">
<value>${password}</value>
</property>
<!--
The default auto-commit state of connections created by this pool.
-->
<property name="defaultAutoCommit">
<value>true</value>
</property>
<!--
The initial number of connections that are created when the pool is started.
Since: 1.2
-->
<property name="initialSize">
<value>5</value>
</property>
<!--
The maximum number of active connections that can be allocated from this pool at the same time, or negative for no limit.
-->
<property name="maxActive">
<value>10</value>
</property>
<!--
The maximum number of connections that can remain idle in the pool, without extra ones being released, or negative for no limit.
-->
<property name="maxIdle">
<value>10</value>
</property>
<!--
The minimum number of connections that can remain idle in the pool, without extra ones being created, or zero to create none.
-->
<property name="minIdle">
<value>0</value>
</property>
<!--
The maximum number of milliseconds that the pool will wait (when there are no available connections) for a connection to be returned before throwing an exception, or -1 to wait indefinitely.
-->
<property name="maxWait">
<value>-1</value>
</property>
<!--
The SQL query that will be used to validate connections from this pool before returning them to the caller. If specified, this query MUST be an SQL SELECT statement that returns at least one row.
-->
<property name="validationQuery">
<value>SELECT 1</value>
</property>
<!--
The indication of whether objects will be validated before being borrowed from the pool. If the object fails to validate, it will be dropped from the pool, and we will attempt to borrow another.
NOTE - for a true value to have any effect, the validationQuery parameter must be set to a non-null string.
-->
<property name="testOnBorrow">
<value>true</value>
</property>
<!--
The indication of whether objects will be validated before being returned to the pool.
NOTE - for a true value to have any effect, the validationQuery parameter must be set to a non-null string.
-->
<property name="testOnReturn">
<value>false</value>
</property>
<!--
The indication of whether objects will be validated by the idle object evictor (if any). If an object fails to validate, it will be dropped from the pool.
NOTE - for a true value to have any effect, the validationQuery parameter must be set to a non-null string.
-->
<property name="testWhileIdle">
<value>false</value>
</property>
<!--
The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no idle object evictor thread will be run.
-->
<property name="timeBetweenEvictionRunsMillis">
<value>-1</value>
</property>
<!--
The number of objects to examine during each run of the idle object evictor thread (if any).
-->
<property name="numTestsPerEvictionRun">
<value>3</value>
</property>
<!--
The minimum amount of time an object may sit idle in the pool before it is eligable for eviction by the idle object evictor (if any).
-->
<property name="minEvictableIdleTimeMillis">
<value>1800000</value>
</property>
<!--
Enable prepared statement pooling for this pool.
-->
<property name="poolPreparedStatements">
<value>false</value>
</property>
<!--
The maximum number of open statements that can be allocated from the statement pool at the same time, or zero for no limit.
-->
<property name="maxOpenPreparedStatements">
<value>0</value>
</property>
<!--
Controls if the PoolGuard allows access to the underlying connection.
-->
<property name="accessToUnderlyingConnectionAllowed">
<value>false</value>
</property>
<!--
Flag to remove abandoned connections if they exceed the removeAbandonedTimout.
If set to true a connection is considered abandoned and eligible for removal if it has been idle longer than the removeAbandonedTimeout. Setting this to true can recover db connections from poorly written applications which fail to close a connection.
-->
<property name="removeAbandoned">
<value>false</value>
</property>
<!--
Timeout in seconds before an abandoned connection can be removed.
-->
<property name="removeAbandonedTimeout">
<value>300</value>
</property>
<!--
Flag to log stack traces for application code which abandoned a Statement or Connection.
Logging of abandoned Statements and Connections adds overhead for every Connection open or new Statement because a stack trace has to be generated.
-->
<property name="logAbandoned">
<value>false</value>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!--
Implementation of the TransactionAttributeSource interface for working with transaction metadata
in JDK 1.5+ annotation format.
This class reads Spring's JDK 1.5+ Transactional annotation and exposes corresponding transaction
attributes to Spring's transaction infrastructure. Can also be used as base class for a custom
annotation-based TransactionAttributeSource.
-->
<bean id="transactionAttributeSource" class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource" />
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager" />
<property name="transactionAttributeSource" ref="transactionAttributeSource" />
</bean>
<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
<property name="order" value="3"></property>
<property name="transactionInterceptor" ref="transactionInterceptor" />
</bean>
</beans>
OBS: Não sei se é a melhor forma de se configurar (podem criticar)
Tenho um framework que faz a persistência e utilizando o método abaixo:
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public DomainObject save(DomainObject object) throws Exception {
Session session = getSession();
try {
if (object.isTransient()) {
session.save(object);
return object;
} else {
return (DomainObject) getSession().merge(object);
}
} catch (Exception e) {
ThrowExceptionHelper.throwDAOException(e);
}
return null;
}
Ao salvar o Usuario e o mesmo ferir uma contraint de unicidade no nome por exemplo, o banco de dados permanece intacto. Mas a propriedade user de Usuario passa a ter id e versao. O formulario é carregado com a mensagem de erro.
Quando as correcoes sao feitas no formulario e tenta-se incluir novamente, por o user possuir um id que nao existe na tabela um erro na fk de Usuario com User no banco de dados é lançado.
Como faço para que a sessão reconheça que houve algum problema e os objetos precisam retornar ao seu estado normal. Como o spring esta controlando a transação (AOP) o hibernate não sabe.
Obrigado.