Java Envers:Spring MVC项目上的审核表错误
我正在将Java Envers:Spring MVC项目上的审核表错误,java,spring,hibernate,spring-mvc,spring-data,Java,Spring,Hibernate,Spring Mvc,Spring Data,我正在将Spring数据JPA1.6.4与hibernate4.3.6.Final+envers一起使用到Spring MVC4.0.7web应用程序中,该应用程序由Spring Security 3.2.5保护。web应用程序部署在Tomcat 7.0.52上 使用JNDI数据源配置的web容器: <Resource name="jdbc/appDB" auth="Container" factory
Spring数据JPA1.6.4
与hibernate4.3.6.Final
+envers
一起使用到Spring MVC4.0.7
web应用程序中,该应用程序由Spring Security 3.2.5
保护。web应用程序部署在Tomcat 7.0.52上
使用JNDI数据源配置的web容器:
<Resource
name="jdbc/appDB"
auth="Container"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
type="javax.sql.DataSource"
initialSize="4"
maxActive="8"
maxWait="10000"
maxIdle="8"
minIdle="4"
username="user"
password="password"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://ip/schema?zeroDateTimeBehavior=convertToNull"
testOnBorrow="true"
testWhileIdle="true"
validationQuery="select 1"
validationInterval="300000" />
奇怪的是,U1是id=6的实体的所有者(而不是id=23的实体的所有者!),而U2实际上在处理实体id 23。问题是修订表不一致,然后Hibernate断言失败
似乎只有在envers创建第三行时才可以。但是为什么它同时创建第一个(使用action CREATE)和第二个(使用action DELETE)
这将禁止用户更新实体
我的问题是调查这是如何发生的
这里是客户
域:
@SuppressWarnings("serial")
@Entity
@Audited
public class Customer extends AbstractDomain{
@ManyToOne(optional=false)
@JoinColumn(updatable=false, nullable=false)
@JsonIgnore
private Company company;
@OneToMany(mappedBy="customer", cascade=CascadeType.REMOVE)
private Set<Plant> plants = new HashSet<Plant>();
@Enumerated(EnumType.STRING)
@Column(nullable=false)
private CustomerType customerType;
private String code;
// other basic fields + getter and settes
}
下面是AbstractDomain
类:
@SuppressWarnings("serial")
@MappedSuperclass
@Audited
public abstract class AbstractDomain implements Auditable<String, Long>, Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Version
@JsonIgnore
private int version;
@JsonIgnore
@Column(updatable=false)
private String createdBy;
@Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
@DateTimeFormat(iso=ISO.DATE_TIME)
@JsonIgnore
@Column(updatable=false)
private DateTime createdDate;
@JsonIgnore
private String lastModifiedBy;
@Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
@DateTimeFormat(iso=ISO.DATE_TIME)
@JsonIgnore
private DateTime lastModifiedDate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
@Override
public String getCreatedBy() {
return createdBy;
}
@Override
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
@Override
public DateTime getCreatedDate() {
return createdDate;
}
@Override
public void setCreatedDate(DateTime createdDate) {
this.createdDate = createdDate;
}
@Override
public String getLastModifiedBy() {
return lastModifiedBy;
}
@Override
public void setLastModifiedBy(String lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
@Override
public DateTime getLastModifiedDate() {
return lastModifiedDate;
}
@Override
public void setLastModifiedDate(DateTime lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
@Transient
@Override
public final boolean isNew() {
if (id == null) {
return true;
} else {
return false;
}
}
}
这是我的CustomerRepository
public interface CustomerRepository extends PagingAndSortingRepository<Customer, Long>, QueryDslPredicateExecutor<Customer> {
}
我的SecurityUtils
class
public class SecurityUtils {
private SecurityUtils(){}
private static Logger LOGGER = LoggerFactory.getLogger(SecurityUtils.class);
public static Customer getCustomer() {
Customer customer = null;
if (SecurityContextHolder.getContext().getAuthentication()!=null) {
customer = ((User)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getCustomer();
LOGGER.debug("Customer found: "+customer.getUserName());
}else {
LOGGER.debug("Customer not bound.");
}
return customer;
}
public static boolean isUserInRole(String role) {
for (GrantedAuthority grantedAuthority : SecurityContextHolder.getContext().getAuthentication().getAuthorities()) {
if (grantedAuthority.getAuthority().equals(role)) {
return true;
}
}
return false;
}
}
最后是xml jpa配置:
<?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:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="emf"/>
</bean>
<bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />
<property name="packagesToScan" value="scan.domain"/>
<property name="persistenceUnitName" value="persistenceUnit"/>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
<!--${hibernate.format_sql} -->
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
<!-- ${hibernate.show_sql} -->
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.connection.charSet">UTF-8</prop>
<prop key="hibernate.max_fetch_depth">3</prop>
<prop key="hibernate.jdbc.fetch_size">50</prop>
<prop key="hibernate.jdbc.batch_size">20</prop>
<prop key="jadira.usertype.databaseZone">jvm</prop>
<prop key="org.hibernate.envers.audit_table_suffix">_H</prop>
<prop key="org.hibernate.envers.revision_field_name">AUDIT_REVISION</prop>
<prop key="org.hibernate.envers.revision_type_field_name">ACTION_TYPE</prop>
<prop key="org.hibernate.envers.audit_strategy">org.hibernate.envers.strategy.ValidityAuditStrategy</prop>
<prop key="org.hibernate.envers.audit_strategy_validity_end_rev_field_name">AUDIT_REVISION_END</prop>
<prop key="org.hibernate.envers.audit_strategy_validity_store_revend_timestamp">True</prop>
<prop key="org.hibernate.envers.audit_strategy_validity_revend_timestamp_field_name">AUDIT_REVISION_END_TS</prop>
</props>
</property>
</bean>
<jpa:repositories base-package="scan.repository"
entity-manager-factory-ref="emf"
transaction-manager-ref="transactionManager"/>
<jpa:auditing auditor-aware-ref="auditorAwareBean" />
<bean id="auditorAwareBean" class="auditor.AuditorAwareBean"/>
</beans>
${hibernate.dial}
org.hibernate.cfg.ImprovedNamingStrategy
真的
${hibernate.hbm2ddl.auto}
假的
UTF-8
3.
50
20
虚拟机
_H
审核和修订
动作类型
org.hibernate.envers.strategy.ValidityAuditStrategy
审核(修订)(完)
真的
审核修订结束
在这个项目中,我有大约50个域类,其中一些具有继承单表
该应用程序现在由少数用户使用,这些用户没有同时连接。所以我可以说在给定的时间只有一个用户在使用我的应用程序
我也不明白如何不安全地使用会话
。我从不直接使用Hibernate会话。我总是对Spring数据存储库使用更高级别的抽象。有时我需要扩展JpaRepository
接口,以便调用saveAndFlush()
或显式调用flush()
。也许这就是原因
我不能理解这种行为!任何建议都将不胜感激 遇到一些麻烦后,我找到了解决办法:
mysql 5.5指南规定:
InnoDB使用内存中的自动增量计数器,只要服务器
跑。当服务器停止并重新启动时,InnoDB将重新初始化
第一次插入表时每个表的计数器,如下所示
如前所述
这对我来说是个大问题。我正在使用envers进行实体审计。我得到的错误和我删除的“最后一行”一样多
假设我开始将数据插入一个空表。假设插入10行。然后假设删除最后8个。在我的数据库中,我将有两个实体,分别id为1和2。在审计表中,我将拥有id为1到10的所有10个实体,id为3到10的实体将拥有2个操作:创建操作和删除操作
自动递增计数器现在设置为11。重新启动mysql服务自动递增计数器变为3。因此,如果我插入一个新实体,它将以id 3保存。但在审计表中还有一个id为3的实体。该实体已标记为已创建和已删除。这会导致在更新/删除操作期间断言失败,因为envers无法处理这种不一致的状态。您对用户的配置和存储似乎有问题。当旧用户仍然是旧公司的一部分时,您可能会将该公司添加到另一个用户。这是因为您正在处理分离的实例,可能还有附加的实例。这可能会带来问题。谢谢@M.Deinum!那么你认为问题可能出在客户安全服务上?在这里,我检查客户(存储在SecurityContextHolder中)是否是实体的所有者,以及他是否可以更新实体。为了检查实体,我从数据库中获取它(实体来自视图中的更新,我不将不可更新字段映射到隐藏字段)。然后我比较它们。如果我理解你的评论,你是说在数据库中查询实体以便在继续保存之前进行比较是个问题,我是对的?@m.Deinum你指的是这个问题吗?你说你找到了解决方案,但是。。。这是怎么一回事?我现在面临着一个类似的问题:-/是的。。解决方案不是一个恰当的词。我发现了为什么会这样。解决方案可能是执行一个脚本,将每个表的基值eq自动递增为相对审核表的max+1。@Kazbeel显然只有在mysql服务重新启动时才需要执行此操作。。希望这有帮助!有关解决方案,请参见此处的答案:
@Service
@Repository
@Transactional(readOnly=true)
public class CustomerServiceImpl implements CustomerService{
@Autowired
private CustomerRepository customerRepository;
@Override
@PostAuthorize("@customerSecurityService.checkAuth(returnObject)")
public Customer findById(Long id) {
return customerRepository.findOne(id);
}
@Override
@PreAuthorize("isAuthenticated()")
@Transactional(readOnly=false)
public Customer create(Customer entry) {
entry.setCompany(SecurityUtils.getCustomer().getCompany());
return customerRepository.save(entry);
}
@Override
@PreAuthorize("@customerSecurityService.checkAuth(#entry)")
@Transactional(readOnly=false)
public Customer update(Customer entry) {
return customerRepository.save(entry);
}
....
}
public interface CustomerRepository extends PagingAndSortingRepository<Customer, Long>, QueryDslPredicateExecutor<Customer> {
}
@Component
@Transactional(readOnly=true)
public class CustomerSecurityService {
Logger LOGGER = LoggerFactory.getLogger(CustomerSecurityService.class);
@Autowired
private CustomerRepository customerRepository;
public boolean checkAuth(Customer customer) {
if(customer == null) {
LOGGER.error("customer NULL!");
return false;
}
if (customer.getId()==null) {
return true;
}
if (customer.getId()!=null) {
Customer dbCustomer = customerRepository.findOne(customer.getId());
if (dbCustomer.getCompany().getId().equals( SecurityUtils.getCustomer().getCompany().getId())){
return true;
}else {
return false;
}
}
return false;
}
public boolean checkPage(Page<Customer> pages) {
for(Customer customer : pages.getContent()) {
Customer dbCustomer = customerRepository.findOne(customer.getId());
if (!dbCustomer.getCompany().getId().equals(SecurityUtils.getCustomer().getCompany().getId())){
return false;
}
}
return true;
}
}
public class SecurityUtils {
private SecurityUtils(){}
private static Logger LOGGER = LoggerFactory.getLogger(SecurityUtils.class);
public static Customer getCustomer() {
Customer customer = null;
if (SecurityContextHolder.getContext().getAuthentication()!=null) {
customer = ((User)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getCustomer();
LOGGER.debug("Customer found: "+customer.getUserName());
}else {
LOGGER.debug("Customer not bound.");
}
return customer;
}
public static boolean isUserInRole(String role) {
for (GrantedAuthority grantedAuthority : SecurityContextHolder.getContext().getAuthentication().getAuthorities()) {
if (grantedAuthority.getAuthority().equals(role)) {
return true;
}
}
return false;
}
}
<?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:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="emf"/>
</bean>
<bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />
<property name="packagesToScan" value="scan.domain"/>
<property name="persistenceUnitName" value="persistenceUnit"/>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
<!--${hibernate.format_sql} -->
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
<!-- ${hibernate.show_sql} -->
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.connection.charSet">UTF-8</prop>
<prop key="hibernate.max_fetch_depth">3</prop>
<prop key="hibernate.jdbc.fetch_size">50</prop>
<prop key="hibernate.jdbc.batch_size">20</prop>
<prop key="jadira.usertype.databaseZone">jvm</prop>
<prop key="org.hibernate.envers.audit_table_suffix">_H</prop>
<prop key="org.hibernate.envers.revision_field_name">AUDIT_REVISION</prop>
<prop key="org.hibernate.envers.revision_type_field_name">ACTION_TYPE</prop>
<prop key="org.hibernate.envers.audit_strategy">org.hibernate.envers.strategy.ValidityAuditStrategy</prop>
<prop key="org.hibernate.envers.audit_strategy_validity_end_rev_field_name">AUDIT_REVISION_END</prop>
<prop key="org.hibernate.envers.audit_strategy_validity_store_revend_timestamp">True</prop>
<prop key="org.hibernate.envers.audit_strategy_validity_revend_timestamp_field_name">AUDIT_REVISION_END_TS</prop>
</props>
</property>
</bean>
<jpa:repositories base-package="scan.repository"
entity-manager-factory-ref="emf"
transaction-manager-ref="transactionManager"/>
<jpa:auditing auditor-aware-ref="auditorAwareBean" />
<bean id="auditorAwareBean" class="auditor.AuditorAwareBean"/>
</beans>