Spring jpa setup не может обновить базу данных - PullRequest
2 голосов
/ 12 февраля 2011

Я давний читатель, первый постер.

По сути, я пишу веб-приложение, основанное на витрине spring 3 .

Впоследствии я включил возможность записи в базу данных на основе этого урока .

Мои интеграционные тесты, в которых тестируются все компоненты ORM, выглядят счастливыми, однако, когда я развертываю свой проект на tomcat и пытаюсь выполнить обновление базы данных через веб-сайт, база данных не обновляется!

Конфигурация:

appContext.xml

<!-- holding properties for database connectivity / -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- enabling annotation driven configuration / -->
<context:annotation-config />
<context:component-scan base-package="wcpackage" />
<tx:annotation-driven transaction-manager="transactionManager" />

<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

<bean id="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource"
    p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}" />

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
    p:entityManagerFactory-ref="entityManagerFactory" />

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
    p:dataSource-ref="dataSource" p:jpaVendorAdapter-ref="jpaAdapter">
    <property name="loadTimeWeaver">
        <bean
            class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
    <property name="persistenceUnitName" value="wctemplatePU"></property>
</bean>

<bean id="jpaAdapter"
    class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
    p:showSql="${jpa.showSql}" />

jdbc.properties

jpa.database=gm
jpa.showSql=true
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/gm?user=gmuser&password=gmuser

persistence.xml

<persistence 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">

<persistence-unit name="wctemplatePU" transaction-type="RESOURCE_LOCAL">
    <properties>
        <property name="hibernate.hbm2ddl.auto" value="validate" />
    </properties>
</persistence-unit>

Код, который должен фиксироваться в БД

    @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
protected Sequence persistGame(PGNGame game) {
    Sequence seq = new Sequence();
    seq.setType(SequenceType.GAME);
    seq.setEvent(game.getTag("Event"));
    seq.setSite(game.getTag("Site"));

            ...

    sequenceDao.persist(seq);

    return seq;
}

SequenceDao по сути

public abstract class JpaDao<K, E> implements Dao<K, E> {
protected Class<E> entityClass;

@PersistenceContext
protected EntityManager entityManager;

public JpaDao() {
    ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
    this.entityClass = (Class<E>) genericSuperclass.getActualTypeArguments()[1];
}

public void persist(E entity) { entityManager.persist(entity); }

public void remove(E entity) { entityManager.remove(entity); }

public E findById(K id) { return entityManager.find(entityClass, id); }
}

Я не вижу никаких ошибок, которые я вижу в консоли / журналах. Что я делаю не так?

Одна вещь, которую я замечаю, когда запускаю запись в спящем режиме на DEBUG

DEBUG:org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler - Creating new EntityManager for shared EntityManager invocation
    DEBUG: org.hibernate.impl.SessionImpl - opened session at timestamp: 12975078282
    DEBUG: org.hibernate.event.def.AbstractSaveEventListener - delaying identity-insert due to no transaction in progress
    DEBUG: org.hibernate.event.def.AbstractSaveEventListener - delaying identity-insert due to no transaction in progress
    DEBUG: org.springframework.orm.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager

TRACE log is

DEBUG:org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler - Creating new EntityManager for shared EntityManager invocation
DEBUG: org.hibernate.impl.SessionImpl - opened session at timestamp: 12975085724
TRACE: org.hibernate.engine.IdentifierValue - id unsaved-value: 0
TRACE: org.hibernate.event.def.AbstractSaveEventListener - transient instance of: net.samuelbergin.gm.model.Player
TRACE: org.hibernate.event.def.DefaultPersistEventListener - saving transient instance
TRACE: org.hibernate.event.def.AbstractSaveEventListener - saving [net.samuelbergin.gm.model.Player#<null>]
TRACE: org.hibernate.engine.Cascade - processing cascade ACTION_PERSIST_SKIPLAZY for: net.samuelbergin.gm.model.Player
TRACE: org.hibernate.engine.Cascade - done processing cascade ACTION_PERSIST_SKIPLAZY for: net.samuelbergin.gm.model.Player
TRACE: org.hibernate.event.def.WrapVisitor - Wrapped collection in role: net.samuelbergin.gm.model.Player.sequenceList
DEBUG: org.hibernate.event.def.AbstractSaveEventListener - delaying identity-insert due to no transaction in progress
TRACE: org.hibernate.engine.Cascade - processing cascade ACTION_PERSIST_SKIPLAZY for: net.samuelbergin.gm.model.Player
TRACE: org.hibernate.engine.Cascade - cascade ACTION_PERSIST_SKIPLAZY for collection: net.samuelbergin.gm.model.Player.sequenceList
TRACE: org.hibernate.engine.CascadingAction - cascading to persist: net.samuelbergin.gm.model.Sequence
TRACE: org.hibernate.engine.IdentifierValue - id unsaved-value: 0
TRACE: org.hibernate.event.def.AbstractSaveEventListener - transient instance of: net.samuelbergin.gm.model.Sequence
TRACE: org.hibernate.event.def.DefaultPersistEventListener - saving transient instance
TRACE: org.hibernate.event.def.AbstractSaveEventListener - saving [net.samuelbergin.gm.model.Sequence#<null>]
TRACE: org.hibernate.engine.Cascade - processing cascade ACTION_PERSIST_SKIPLAZY for: net.samuelbergin.gm.model.Sequence
TRACE: org.hibernate.engine.Cascade - done processing cascade ACTION_PERSIST_SKIPLAZY for: net.samuelbergin.gm.model.Sequence
TRACE: org.hibernate.event.def.WrapVisitor - Wrapped collection in role: net.samuelbergin.gm.model.Sequence.commentList
TRACE: org.hibernate.engine.IdentifierValue - id unsaved-value: 0
DEBUG: org.hibernate.event.def.AbstractSaveEventListener - delaying identity-insert due to no transaction in progress
TRACE: org.hibernate.engine.Cascade - processing cascade ACTION_PERSIST_SKIPLAZY for: net.samuelbergin.gm.model.Sequence
TRACE: org.hibernate.engine.Cascade - cascade ACTION_PERSIST_SKIPLAZY for collection: net.samuelbergin.gm.model.Sequence.commentList
TRACE: org.hibernate.engine.Cascade - done cascade ACTION_PERSIST_SKIPLAZY for collection: net.samuelbergin.gm.model.Sequence.commentList
TRACE: org.hibernate.engine.Cascade - done processing cascade ACTION_PERSIST_SKIPLAZY for: net.samuelbergin.gm.model.Sequence
TRACE: org.hibernate.engine.Cascade - done cascade ACTION_PERSIST_SKIPLAZY for collection: net.samuelbergin.gm.model.Player.sequenceList
TRACE: org.hibernate.engine.Cascade - done processing cascade ACTION_PERSIST_SKIPLAZY for: net.samuelbergin.gm.model.Player
DEBUG: org.springframework.orm.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
TRACE: org.hibernate.impl.SessionImpl - closing session

Определение сущности

@Entity
@Table(name="sequences")
public class Sequence {
    @Id
    @GeneratedValue
    private int id;
    @Enumerated(EnumType.STRING)
    private SequenceType type;
    private String event;
    private String site;
    @Temporal(TemporalType.DATE)
    private Date date;
    private int round;
    private String result;
    private String eco;
    private String description;
    private String moves;
    @ManyToOne
    private Player whitePlayer;
    @ManyToOne
    private Player blackPlayer;
    @OneToMany(cascade={CascadeType.ALL})
    @JoinTable(name="sequenceComments",
            joinColumns={@JoinColumn(name="sequence_fk")},
            inverseJoinColumns={@JoinColumn(name="comment_fk")})
    private List<Comment> commentList = new ArrayList<Comment>();
        ...

Контроллер

@Controller
@RequestMapping("/fileupload")
public class FileUploadController {

    private static Logger logger = LoggerFactory.getLogger(FileUploadController.class);

    @Autowired private SequenceService sequenceService;

    @RequestMapping(method=RequestMethod.POST)
    public void processUpload(@RequestParam MultipartFile file, HttpServletRequest request, Model model) throws IOException {
        //TODO: parse pgn
        List<PGNGame> pgnGameList;
        String filename = file.getOriginalFilename();
        try {
            pgnGameList = PGNProcessor.parse(file.getInputStream());

            sequenceService.createSequences(pgnGameList);

            String message = "File '" + filename + "' uploaded successfully";
            model.addAttribute("message", new Message(MessageType.success, message));

        } catch (PGNProcessorException ppe) {
            logger.error("Failed to process "+filename, ppe);
            model.addAttribute("message", new Message(MessageType.error, "Failed to process "+filename+".  Reason: "+ppe.getMessage()));
        }
        // else whole html pg is rendered instead of just a div to update
        model.addAttribute("ajaxRequest", AjaxUtils.isAjaxUploadRequest(request));  
    }
}

Услуги

public class JpaSequenceService implements SequenceService {

    ...

public List<Sequence> createSequences(List<PGNGame> gameList) {
    List<Sequence> seqList = new ArrayList<Sequence>();
    for (PGNGame game : gameList) {
        Sequence seq = persistGame(game);
        seqList.add(seq);
    }
    return seqList;
}

@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
protected Sequence persistGame(PGNGame game) {
        /*...as above...*/
}

1 Ответ

3 голосов
/ 12 февраля 2011

@Transactional работает только в том случае, если он размещен в публичном методе, который вызывается из других объектов (а не внутри того же объекта).

Транзакции обрабатываются путем создания прокси вокруг объекта. Звонки извне перехватываются прокси, и, следовательно, транзакция запускается. Вызовы внутри одного и того же объекта не перехватываются прокси.

Так в вашем случае:

@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
public List<Sequence> createSequences(List<PGNGame> gameList) {

Обратите внимание, что, как правило, рекомендуется переносить единицы работы в транзакцию. Даже если ваш код сработает, он будет излишне открывать и закрывать несколько транзакций. Если вы не хотели сохранять каждую игру в отдельности. Если это так, то либо итерируйте список в контроллере, либо создайте новый класс, который будет сохранять игры.

Как заметил Аугусто, если вы используете proxy-target-class="true" для своих прокси, вы сможете выполнить внутренний вызов для запуска транзакции. Однако я не одобряю это, поскольку вы становитесь зависимыми от этого параметра конфигурации, и если вы измените его в будущем по какой-либо другой причине, вы можете потратить время на выяснение того, почему существующие функции перестали работать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...