Borys Pawluczuk

Borys Pawluczuk programista
web/mvc/rest

Temat: Problem z optimistic locking przy odczycie danych z bazy

Mam następujący problem chciałbym założyć blokadę OPTIMISTIC na odczyt danych z bazy natomiast na zapis blokadę OPTIMISTIC_FORCE_INCREMENT i tak ustawiony zapis do bazy działa. To wszystko na potrzeby generycznego repozytorium, poniżej jest tylko uproszczona wersja bez generyka, ale też nie działa, także wystarczy jako przykład. W pliku Main.java są dwie wersje kodu, którym pobieram dane jeden wykorzystujący adnotację @Transaction (CustommersDao.java), a drugi samodzielnie zarządza transakcją (ustawienia z persistence.xml) i w tym przypadku jest wszystko ok. Ale chodzi mi o część z @Transaction. Baza postgres (dla MySql było to samo) ustawiona na poziomie READ COMMITTED Wszystkie pliki *.java są w jednym pakiecie.

Ps. Jeśli na odczyt założę blokadę OPTIMISTIC_FORCE_INCREMENT to też jest ok tylko samo OPTIMISTIC generuje błąd.

Po uruchomieniu programu otrzymuję błąd:

Exception in thread "main" org.springframework.dao.InvalidDataAccessResourceUsageException: could not retrieve version: [com.example.main.Custommers#3]; SQL [select "version" from "custommers" where "id" =?]; nested exception is org.hibernate.exception.SQLGrammarException: could not retrieve version: [com.example.main.Custommers#3]

(...)


Proszę Was o wskazówki na temat tego kodu co może być nie tak jak trzeba.

AppConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({ "com.example.main" })
public class AppConfig {
@Bean("custommersDao")
public CustommersDao custommers() {
return new CustommersDao();
}
}


PersistanceJpaConfig.java

import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.example.main")
@PropertySource({ "classpath:application.properties" })
public class PersistenceJPAConfig {

@Autowired
private Environment env;

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { env.getProperty("entitymanager.packagesToScan") });

JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());

return em;
}

@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("db.driverClassName"));
dataSource.setUrl(env.getProperty("db.url"));
dataSource.setUsername(env.getProperty("db.username"));
dataSource.setPassword(env.getProperty("db.password"));
return dataSource;
}

@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setDataSource(dataSource());
transactionManager.setEntityManagerFactory(emf);

return transactionManager;
}

@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}

Properties additionalProperties() {

Properties properties = new Properties();

properties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
properties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
properties.setProperty("hibernate.globally_quoted_identifiers", env.getProperty("hibernate.globally_quoted_identifiers"));
properties.setProperty("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
properties.setProperty("hibernate.format_sql", env.getProperty("hibernate.format_sql"));

return properties;
}
}


Main.java

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.LockModeType;
import javax.persistence.OptimisticLockException;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

public static void main(String[] args) {

//Baza jest READ COMMITTED
//Tutaj otrzymuję błąd sql (korzystam z adnotacji @Transaction) w pliku CustommersDao.java można zobaczyć ustawienia LockModeType
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

CustommersDao custommersDao = context.getBean("custommersDao", CustommersDao.class);
custommersDao.load(3L);

//Tutaj nie ma żadnego problemu wszystko działa (korzystam z pliku persistance.xml) ustawienia LockModeType takie same jak w CustommersDao.java
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("persistence");
EntityManager em = entityManagerFactory.createEntityManager();

EntityTransaction txn = em.getTransaction();
txn.begin();
try {

Custommers entity1 = em.find(Custommers.class, 3L, LockModeType.OPTIMISTIC);
System.out.println("[[" + entity1.getVersion() + "]]");
Custommers entity2 = em.find(Custommers.class, 3L, LockModeType.OPTIMISTIC_FORCE_INCREMENT); System.out.println("[[" + entity2.getVersion() + "]]");
txn.commit();

} catch (OptimisticLockException ex) {

System.out.println("OptimisticLockException: " + ex.toString());

} catch (PersistenceException ex) {

System.out.println("PersistenceException: " + ex.toString());

} finally {
if (txn.isActive())
txn.rollback();
}
}
}


CustommerDao.java

import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.PersistenceContext;

import org.springframework.transaction.annotation.Transactional;

@Transactional
public class CustommersDao {

@PersistenceContext
EntityManager entityManager;
public Custommers load(Long id) {

Custommers entity = entityManager.find(Custommers.class, id, LockModeType.OPTIMISTIC);
return entity;
}

public void save(Custommers aggregate) {

if (entityManager.contains(aggregate)) {
entityManager.lock(aggregate, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
} else {
entityManager.persist(aggregate);
}
}
}


Custommer.java

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;

@Entity
@Table(name = "custommers")
public class Custommers{

@Id
@Column(name="id")
@GeneratedValue
private Long id;

@Version
private Long version;
public Long getId() {
return id;
}

public Long getVersion() {
return version;
}
}


persistance.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 http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="persistence">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>com.example.main.Custommers</class>
<properties>
<property name="hibernate.connection.driver_class" value="org.postgresql.Driver" />
<property name="hibernate.connection.url" value="jdbc:postgresql://localhost:5432/shop" />
<property name="hibernate.connection.username" value="postgres" />
<property name="hibernate.connection.password" value="postgres" />
</properties>
</persistence-unit>
</persistence>


application.properties

# Database
db.driverClassName: org.postgresql.Driver
db.url: jdbc:postgresql://localhost:5432/shop
db.username: postgres
db.password: postgres

# Hibernate
hibernate.dialect: org.hibernate.dialect.PostgreSQLDialect
hibernate.globally_quoted_identifiers: true
hibernate.show_sql: true
hibernate.format_sql: true
hibernate.hbm2ddl.auto: update
entitymanager.packagesToScan: com.example.main


pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>java.spring.generic.repository</artifactId>
<version>0.0.1-SNAPSHOT</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<springframework.version>4.3.6.RELEASE</springframework.version>
<hibernate.version>4.3.5.Final</hibernate.version>
<mysql.connector.version>5.1.31</mysql.connector.version>
</properties>

<dependencies>

<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.11.0.RELEASE</version>
</dependency>


<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>


<!-- Spring JDBC and PostgreSQL Driver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.3-1101-jdbc41</version>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

</dependencies>

</project>
Ten post został edytowany przez Autora dnia 16.03.17 o godzinie 13:38

konto usunięte

Temat: Problem z optimistic locking przy odczycie danych z bazy

A co konkretnie jest do osiągnięcia?

Na pierwszy rzut oka... blokady optimistic się nie zakłada. W sensie to jest pattern po stronie klieta, który jest w stanie wyłapać sytuację, w której ktoś inny zmienił dane. Jak sobie klient obsłuży - tak ma.
Podejść do tematu jest parę - tu jest przykładowy opis:
https://en.wikibooks.org/wiki/Java_Persistence/Locking - jest seksja JPA 2.0 Locking, ale chyba warto przebrnąć przez całość.
Tak, czy owak. Jeżeli problemem jest wywalanie się transakcji odczytującej - zrezygnowałbym z force increment. Jeżeli jednak to ma być tak, że stara wersja zwyczajnie zostaje - wtedy trzeba długą transakcję zrobić, ustawić ją na repetable read, albo serializable - i wtedy będzie widzieć stare dane.



Wyślij zaproszenie do