- Резюме (сокращено) -
У меня есть контроллер, который загружает объект профиля из соответствующего DAO. Он обновляет некоторые свойства, многие из них устанавливаются, а затем вызывает saveOrUpdate (через save в DAO) для повторного присоединения и обновления объекта профиля. Через, казалось бы, случайные интервалы мы получаем org.hibernate.exception.ConstraintViolationException с основной причиной: вызвано: java.sql.BatchUpdateException: повторяющаяся запись '3-56' для ключа 1. Трассировка стека указывает на метод saveOrUpdate, который называется из контроллера обновления профиля. Я не могу выполнить репликацию в моей тестовой среде, мы видим это только в производственной среде, поэтому мне интересно, если я что-то упустил из-за безопасности потоков (именно поэтому я публикую так много информации о коде / конфигурации). Есть идеи?
- Код -
Я пытался предоставить как можно больше соответствующей конфигурации / кода - дайте мне знать, если потребуется больше:
Вот выдержка из нарушающего контроллера:
public class EditProfileController extends SimpleFormController {
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception
{
if(!checkLoggedIn(request))
{
return new ModelAndView("redirect:" + invalidRedirect);
}
HttpSession session = request.getSession();
Resource resource = (Resource)session.getAttribute("resource"); //The resource object is stored in session upon login and upon account creation.
Profile profile = profiles.getProfileByResource(resource);
if(profile == null)
{
profile = new Profile();
profile.setResource(resource);
}
//I use custom editors to populate the sets in the command object with objects based on the selection
if(profile.getPrimaryRoleSkills() != null && editProfileCommand.getPrimaryRoleSkills() != null)
{
profile.getPrimaryRoleSkills().addAll(editProfileCommand.getPrimaryRoleSkills());
profile.getPrimaryRoleSkills().retainAll(editProfileCommand.getPrimaryRoleSkills());
}
else
profile.setPrimaryRoleSkills(editProfileCommand.getPrimaryRoleSkills());
profiles.save(profile); //This is the line that appears in the stack trace
return new ModelAndView(getSuccessView());
}
//Other methods omitted
}
Сокращенный профиль класса:
public class Profile implements java.io.Serializable {
private long id;
private Resource resource;
private Set<PrimaryRoleSkill> primaryRoleSkills = new HashSet<PrimaryRoleSkill>(0);
public Profile() {
}
//Other properties trivial or similar to above. Getters and setters omitted
//toString, equals, and hashCode are all generated by hbm2java
}
Базовый класс NameValuePairs (PrimaryRoleSkill расширяет это, ничего не добавляя):
public class NameValuePairs implements java.io.Serializable {
private long id;
private String name;
private boolean active = true;
public NameValuePairs() {
}
//equals and hashCode generated by hbm2java, getters & setters omitted
}
Вот мой базовый класс DAO:
public class DAO {
protected DAO() {
}
public static Session getSession() {
Session session = (Session) DAO.session.get();
if (session == null) {
session = sessionFactory.openSession();
DAO.session.set(session);
}
return session;
}
protected void begin() {
getSession().beginTransaction();
}
protected void commit() {
getSession().getTransaction().commit();
}
protected void rollback() {
try {
getSession().getTransaction().rollback();
} catch( HibernateException e ) {
log.log(Level.WARNING,"Cannot rollback",e);
}
try {
getSession().close();
} catch( HibernateException e ) {
log.log(Level.WARNING,"Cannot close",e);
}
DAO.session.set(null);
}
public boolean save(Object object)
{
try {
begin();
getSession().saveOrUpdate(object);
commit();
return true;
}
catch (HibernateException e) {
log.log(Level.WARNING,"Cannot save",e);
rollback();
return false;
}
}
private static final ThreadLocal<Session> session = new ThreadLocal<Session>();
private static final SessionFactory sessionFactory = new Configuration()
.configure().buildSessionFactory();
private static final Logger log = Logger.getAnonymousLogger();
//Non-related methods omitted.
}
Ниже приведена важная часть профилей DAO:
public class Profiles extends DAO {
public Profile getProfileByResource(Resource resource)
{
try
{
begin();
Query q = getSession().createQuery("from Profile where resource = :resource");
q.setLong("resource", resource.getId());
commit();
if(q.uniqueResult() == null)
return null;
return (Profile) q.uniqueResult();
}
catch(HibernateException e)
{
rollback();
}
return null;
}
//Non-related methods omitted.
}
Соответствующая конфигурация пружины:
<bean id="profiles" class="com.xxxx.dao.Profiles" />
<bean id="editProfileController" class="com.xxxx.controllers.EditProfileController">
<property name="sessionForm" value="false" />
<property name="commandName" value="editProfileCommand" />
<property name="commandClass" value="com.xxxx.commands.EditProfileCommand" />
<property name="profiles" ref="profiles" />
<property name="formView" value="EditProfile" />
<property name="successView" value="redirect:/profile" />
<property name="validator" ref="profileValidator" />
</bean>
hibernate.cfg.xml
<session-factory>
<property name="connection.driver_class">@driver@</property>
<property name="connection.url">@connectionurl@</property>
<property name="connection.username">@dbuser@</property>
<property name="connection.password">@dbpw@</property>
<property name="dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
<property name="dbcp.maxActive">15</property>
<property name="dbcp.maxIdle">5</property>
<property name="dbcp.maxWait">120000</property>
<property name="dbcp.whenExhaustedAction">2</property>
<property name="dbcp.testOnBorrow">true</property>
<property name="dbcp.testOnReturn">true</property>
<property name="dbcp.validationQuery">
select 1
</property>
<property name="dbcp.ps.maxActive">0</property>
<property name="dbcp.ps.maxIdle">0</property>
<property name="dbcp.ps.maxWait">-1</property>
<property name="dbcp.ps.whenExhaustedAction">2</property>
<!-- Echo all executed SQL to stdout
<property name="show_sql">true</property>
-->
<mapping resource="com/xxxx/entity/Resource.hbm.xml"/>
<mapping resource="com/xxxx/entity/Authentication.hbm.xml"/>
<mapping resource="com/xxxx/entity/NameValuePairs.hbm.xml"/>
<mapping resource="com/xxxx/entity/Profile.hbm.xml"/>
<mapping resource="com/xxxx/entity/FileData.hbm.xml"/>
</session-factory>
Выдержка из Profile.hbm.xml:
<hibernate-mapping>
<class name="com.xxxx.entity.Profile" select-before-update="true">
<id name="id" type="long">
<generator class="foreign">
<param name="property">resource</param>
</generator>
</id>
<set name="primaryRoleSkills" cascade="none">
<key column="profile"/>
<many-to-many column="primary_role_skill" class="com.xxxx.entity.PrimaryRoleSkill"/>
</set>
</class>
</hibernate-mapping>
Выдержка из NameValuePairs.hbm.xml:
<hibernate-mapping>
<class name="com.xxxx.entity.NameValuePairs" abstract="true">
<id name="id" type="long">
<generator class="native" />
</id>
<discriminator column="type" type="string" />
<property type="string" name="name" length="256">
<meta attribute="use-in-equals">true</meta>
</property>
<property type="boolean" name="active">
<meta attribute="default-value">true</meta>
</property>
<subclass name="com.xxxx.entity.PrimaryRoleSkill" discriminator-value="PrimaryRoleSkill" />
</class>
</hibernate-mapping>
Приложение работает на Tomcat 6.0.14 и подключается к сообществу MySQL версии 5.0.89, работающему в Linux. Мы используем Hibernate 3.3.2 и Spring Framework 2.5.6.