2010-02-12 2 views
0

--summary (укороченный) -Дубликат ключевой вопрос с Spring и Hibernate - помощь нужна

У меня есть контроллер, который загружает объект профиля из соответствующего DAO. Он обновляет некоторые свойства, многие из них наборы, а затем вызывает saveOrUpdate (через сохранение в 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. 
} 

Ниже является важной частью Profiles 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">@[email protected]</property> 
    <property name="connection.url">@[email protected]</property> 
    <property name="connection.username">@[email protected]</property> 
    <property name="connection.password">@[email protected]</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.

+0

попробуйте включить только соответствующую информацию. слишком долго читать .. – Bozho

+0

см. http://sscce.org/ – Bozho

+1

ваш DAO не должен запускать/фиксировать/управлять транзакциями - бизнес-объекты (службы) должны. – les2

ответ

1

После 10 дней отсутствия исключения я пришел к выводу, что решение, которое я обнаружил, сработало.

Короткий ответ: я переключил свой DAO на использование HibernateTemplate и использовал Spring AOP для обработки транзакций. Это связано с большим переписанием, но оно того стоило, так как решение работает так, как планировалось. Кроме того, я не смог получить ленивую загрузку для работы в своих представлениях JSP, но это не очень важно, так как мои объекты довольно малы (я отключил ленивую загрузку свойств в моей конфигурации Hibernate)

Пояснение : Проблема была в том, как я получал сессию спящего режима. При первоначальной реализации один сеанс Hibernate создается при запуске приложения для каждого DAO, который расширил базовый класс DAO. Это вызвало две проблемы. 1) Спящие сеансы сами по себе не являются потокобезопасными. Вот почему все проверено отлично с одним пользователем на тестовом экземпляре, но было необычное поведение в производстве. 2) MySQL любит закрывать соединение через определенный промежуток времени. Поскольку сеансы проводились непрерывно, это вызывало поврежденные трубы (не сообщается в OP, я думал, что это отдельный вопрос). С этим исправлением Spring теперь управляет созданием/закрытием сеанса, Spring AOP обрабатывает демаркацию транзакций, а SpringTemplate даже обрабатывает большую часть доступа к Hibernate.

1

У меня нет немедленного ответа - это может быть многопоточное условие, или может быть, что вы не используете правильные входные данные в тестовой среде, чтобы вызвать проблему.

Если бы это был мой код, я бы начал с сравнения реализаций equals(), compareTo() и hashCode() с определениями базы данных - если эти методы сравнивают больше полей, чем требует база данных для уникальности, спящий режим может рассмотреть два объекта будут отличаться, даже если они в конечном итоге используют один и тот же ключ в базе данных.

Другим подходом, который я бы рассмотрел, было бы добавить ведение журнала во все места, где эти объекты будут сохранены и извлечены, включая stacktraces (очевидно, с помощью переключателя включения/выключения). Или, альтернативно, когда вы получаете дублируемую ключевую ошибку, запросите базу данных и зарегистрируйте то, что уже там. В любом случае вы хотите узнать, откуда берется «первая» запись.

+0

Спасибо, что ответили! Я еще раз взглянул на методы equals и hashcode, которые генерируются hbm2java. Он включает только поле «имя», которое является бизнес-ключом. Я обязательно займусь регистрацией. –