Гость
Целевая тема:
Создать новую тему:
Автор:
Форумы / Java [игнор отключен] [закрыт для гостей] / Hibernate+Spring transactions. Не понимаю некоторые вещи. / 12 сообщений из 12, страница 1 из 1
20.03.2017, 15:50
    #39423296
justcoder
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate+Spring transactions. Не понимаю некоторые вещи.
Решил поразбираться с транзакциями, а именно с вопросом конкуррентого доступа к данным.
Использую две сущности.

Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
@Entity
public class CarAuction {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
    private int price;
}




Код: java
1.
2.
3.
4.
5.
6.
7.
@Entity
public class PriceHistory {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private int price;
}



Суть транзакции

1)По айди берем цену машины
2)Увеличиваем цену на 10 и обновляем машину
3)В историю цен пишем запись


Реализация

Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
@Service
public class CarAuctionServiceImpl implements CarAuctionService {

    @Autowired
    private CarAuctionDAO carAuctionDAO;

    @Autowired
    private PriceHistoryDAO priceHistoryDAO;


    @Transactional
    public void increasePriceProblem(long carId, int sleep) throws InterruptedException {
        int price = carAuctionDAO.getPrice(carId);
        price = price + 10;
        Thread.sleep(sleep);
        carAuctionDAO.updateCarWithNewPriceWithFlush(carId, price);
        priceHistoryDAO.saveHistory(new PriceHistory(price));
    }
	
    @Transactional
    synchronized public void increasePriceProblemSynchronizedFlush(long carId, int sleep) throws InterruptedException {
        int price = carAuctionDAO.getPrice(carId);
        price = price + 10;
        Thread.sleep(sleep);
        carAuctionDAO.updateCarWithNewPriceWithFlush(carId, price);
        priceHistoryDAO.saveHistory(new PriceHistory(price));
    }


    @Transactional
    synchronized public void increasePriceProblemSynchronizedNoFlush(long carId, int sleep) throws InterruptedException {
        int price = carAuctionDAO.getPrice(carId);
        price = price + 10;
        Thread.sleep(sleep);
        carAuctionDAO.updateCarWithNewPriceNoFlush(carId, price);
        priceHistoryDAO.saveHistoryNoFlush(new PriceHistory(price));
    }
	
	
	@Transactional(isolation = Isolation.SERIALIZABLE)
    public void increasePriceProblemNoFlushSerializible(long carId, int sleep) throws InterruptedException {
        int price = carAuctionDAO.getPrice(carId);
        price = price + 10;
        Thread.sleep(sleep);
        carAuctionDAO.updateCarWithNewPriceNoFlush(carId, price);
        priceHistoryDAO.saveHistoryNoFlush(new PriceHistory(price));
    }

}



Вызов

Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
 private static CarAuctionService carAuctionService;
 private static PriceHistoryService priceHistoryService;

    public static void main(String args[]) throws Exception {
        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
        ctx.load(Constants.CONTEXT_PATH);
        ctx.refresh();
        carAuctionService = ctx.getBean(CarAuctionService.class);
        priceHistoryService = ctx.getBean(PriceHistoryService.class);

        CarAuction car = new CarAuction();
        car.setPrice(10);
        carAuctionService.persistCar(car);

        Thread client1 = new Thread(new Client(1000));
        Thread client2 = new Thread(new Client(2000));

        System.out.println("\n\n");

        client1.start();
        Thread.sleep(200);
        client2.start();

        System.out.println();
        Thread.currentThread().sleep(3000);

        priceHistoryService.soutHistory();
    }


    private static class Client implements Runnable {
        private int sleep;

        public Client(int sleep) {
            this.sleep = sleep;
        }

        public void run() {
            try {
                //carAuctionService.increasePriceProblem(1, sleep);
                //carAuctionService.increasePriceProblemSynchronizedFlush(1, sleep);
                //carAuctionService.increasePriceProblemSynchronizedNoFlush(1, sleep);
                //carAuctionService.increasePriceProblemNoFlushSerializible(1, sleep);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }




Результаты:
1) Вызов
Код: java
1.
 carAuctionService.increasePriceProblem(1, sleep);


Нету syncronized - как итог, метод увеличения цены вызвался два раза а в истории цен записано 20,20.

2)
Код: java
1.
carAuctionService.increasePriceProblemSynchronizedFlush(1, sleep)


Присутствует synchronized и flush в dao, результат корректен, два вызова - две цены 20,30

3) carAuctionService.increasePriceProblemSynchronizedNoFlush(1, sleep);
Присутствует synchronized и НЕТ flush в dao, результат некорректен, два вызова - две цены 20,20.
Вот тут непонятно. Стоит synchronized, вызовы последовательные, а транзакция такое ощущение что по окончанию метода не
закомитилась что ли?

А когда я в цикле вызываю
Код: java
1.
carAuctionService.increasePriceProblemSynchronizedNoFlush(1, sleep)

, то все норм.
Тут получается что транзакция закомитилась после окончания метода, а когда в два потока , то нет. Когда транзакция завершается, по выходу из метода или как?

4)
Код: java
1.
 carAuctionService.increasePriceProblemNoFlushSerializible(1, sleep);


Тут вообще жесть

Код: html
1.
2.
3.
4.
5.
org.springframework.orm.jpa.JpaSystemException: could not execute statement; nested exception is org.hibernate.exception.GenericJDBCException: could not execute statement

Caused by: org.hibernate.exception.GenericJDBCException: could not execute statement

Caused by: java.sql.SQLException: ORA-08177:



Помогите пожалуйста разобраться.
...
Рейтинг: 0 / 0
20.03.2017, 16:24
    #39423348
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate+Spring transactions. Не понимаю некоторые вещи.
Проблема понимания вызвана тем что вы намешали в кучу несколько разных проблем и пытаитесь их понять как "транзакцию". Хотя каждую из этих проблем надо понимать отдельно.
1) Транзакция влияет на то чтобы вы вычитали текущее состояние и на то чтобы вы записали то что вы хотите записать. А за урегулирование многопоточного обновления данных влияют блокировки и неблокирующие алгоритмы, которые транзакциям перпендиклулярны. Блокировки могут быть реализованы как в Java, так и в БД.
2) flush тоже транзакциям немного перпендикулярен.
3) Нет, транзакция не комитится по завершению метода. Spring её закомитит при завершении того метода, в котором он её начал. В вашем примере мне до конца не понятно действительно ли работает @Transactional. Или спринг его проигнорировал.
4) Это не жесть. Это так работает SERIALIZABLE в Oracle. Читаем мануал:
http://docs.oracle.com/cd/E11882_01/server.112/e40540/consist.htm#CNCPT1320
...
Рейтинг: 0 / 0
20.03.2017, 16:54
    #39423395
justcoder
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate+Spring transactions. Не понимаю некоторые вещи.
Спасибо, за ответ и все таки

Транзакции работают нормально, проверил так

Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
@Transactional
    public void testTransaction() {
        CarAuction carAuction = new CarAuction();
        carAuction.setPrice(111);
        carAuctionDAO.persistCar(carAuction);

        carAuction = new CarAuction();
        carAuction.setPrice(222);
        carAuctionDAO.persistCar(carAuction);

        if(true){
            throw new RuntimeException();
        }
    }



Код: java
1.
2.
3.
16:46:21,095 DEBUG JpaTransactionManager:847 - Initiating transaction rollback
16:46:21,095 DEBUG JpaTransactionManager:538 - Rolling back JPA transaction on EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@4dc7cd1c]
16:46:21,101 DEBUG JpaTransactionManager:600 - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@4dc7cd1c] after transaction



BlazkowiczSpring её закомитит при завершении того метода, в котором он её начал.

Тогда мне непонятно, почему вызов вот этих методов дает разные результаты

Код: java
1.
2.
carAuctionService.increasePriceProblemSynchronizedFlush(1, sleep);
carAuctionService.increasePriceProblemSynchronizedNoFlush(1, sleep);



Методы то в обоих случаях из-за synchronized вызываются последовательно, значит результат должен быть в обоих случаях одинаковым, а именно 20 и 30 в таблице истории. И судя из
BlazkowiczSpring её закомитит при завершении того метода, в котором он её начал.
Перед вызовом метода, метод должен видеть результаты камита, но результаты то разные.
...
Рейтинг: 0 / 0
20.03.2017, 17:30
    #39423429
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate+Spring transactions. Не понимаю некоторые вещи.
justcoderМетоды то в обоих случаях из-за synchronized вызываются последовательно, значит результат должен быть в обоих случаях одинаковым, а именно 20 и 30 в таблице истории. И судя из
BlazkowiczSpring её закомитит при завершении того метода, в котором он её начал.
Перед вызовом метода, метод должен видеть результаты камита, но результаты то разные.
Потому как результат не связан с флашем. У вас два потока, две транзакции, два коммита. Если два коммита прошло, значит транзакции прошли. А что именно у вас там комитилось, сказать сложно, так как мы видим только часть кода.
Надо бы ещё понимать что EntityManager это тоже не аналог транзакции.
...
Рейтинг: 0 / 0
20.03.2017, 17:45
    #39423439
justcoder
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate+Spring transactions. Не понимаю некоторые вещи.
BlazkowiczПотому как результат не связан с флашем. У вас два потока, две транзакции, два коммита.
Да, все верно два потока и две транзакции, но код самого потока включает один и тот же объект CarAuctionService, метод которого synchronized , и по окночании которого Spriung должен закомитть транзакциию. Потоки вызывают один и тот же метод одного и того же объекта. И мне непонятно, почему когда один и тот же метод одного и того же объекта вызывается в разных потоках, то транзакция камититься не сразу после завершения метода который помечен аннотацией @Transactional, а когда я тоже самое делает в цикле то все в порядке. Я специально поставил synchronized , а так как объект один и тот же для разных потоков, то вызовы должны идти последовательно, они и идут, а транзакции не камитятся сразу же,после выхода из метода. А если я flush добавляю, то все норм.

Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
//Этот объект общий для двух потоков
private static CarAuctionService carAuctionService;

 private static class Client implements Runnable {
        private int sleep;

        public Client(int sleep) {
            this.sleep = sleep;
        }

        public void run() {
            try {
                //Общий объект вызывается в теле потока
                //carAuctionService.increasePriceProblem(1, sleep);
                //carAuctionService.increasePriceProblemSynchronizedFlush(1, sleep);
                //carAuctionService.increasePriceProblemSynchronizedNoFlush(1, sleep);
                //carAuctionService.increasePriceProblemNoFlushSerializible(1, sleep);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }



Этот код полный.

Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="url" value="${javax.persistence.jdbc.url}"/>
        <property name="driverClassName" value="${javax.persistence.jdbc.driver}"/>
        <property name="username" value="${javax.persistence.jdbc.user}" />
        <property name="password" value="${javax.persistence.jdbc.password}" />
    </bean>


    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="emf"/>
    </bean>

    <tx:annotation-driven/>


    <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="persistenceUnitName" value="myEm"/>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>

        <property name="packagesToScan" value="puzzles.concurrentaccess.entities"/>

        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">create-drop</prop>
            </props>
        </property>
    </bean>




Вот мои DAO
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
@Repository
public class CarAuctionDAOImpl implements CarAuctionDAO {

    @PersistenceContext(unitName = Constants.PERSISTENCE_CONTEXT_UNIT_NAME)
    private EntityManager em;

    @Override
    public int getPrice(long carId) {
        CarAuction car = em.find(CarAuction.class, carId);
        return car.getPrice();
    }

    @Override
    public void updateCarWithNewPriceWithFlush(long id, int newPrice) {
        CarAuction carAuction = em.find(CarAuction.class, id);
        carAuction.setPrice(newPrice);
        em.flush();
    }

    @Override
    public void updateCarWithNewPriceNoFlush(long id, int newPrice) {
        CarAuction carAuction = em.find(CarAuction.class, id);
        carAuction.setPrice(newPrice);
    }


    @Override
    public void persistCar(CarAuction car) {
        em.persist(car);
    }

}




Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
@Repository
public class PriceHistoryDAOImpl implements PriceHistoryDAO {

    @PersistenceContext(unitName = Constants.PERSISTENCE_CONTEXT_UNIT_NAME)
    private EntityManager em;

    @Override
    public void saveHistory(PriceHistory priceHistory) {
        em.persist(priceHistory);
        em.flush();
    }

    @Override
    public void saveHistoryNoFlush(PriceHistory priceHistory) {
        em.persist(priceHistory);
    }

    @Override
    public List<PriceHistory> getAllHistoryItems() {
        return em.createQuery("FROM PriceHistory", PriceHistory.class).getResultList();
    }
}
...
Рейтинг: 0 / 0
20.03.2017, 17:50
    #39423447
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate+Spring transactions. Не понимаю некоторые вещи.
Потому что flush меняет порядок записи в базу.
В случае с flush вы сразу пытаетесь сохранить изменение в БД.
В случае без явного flush, запись в БД происходит позже - во время закрытие EntityManager-а и коммита транзакции.
Чтобы понять как именно влияет flush, стоит посмотреть порядок запросов в логе и сравнить.
...
Рейтинг: 0 / 0
20.03.2017, 17:55
    #39423453
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate+Spring transactions. Не понимаю некоторые вещи.
Разница в том что scope транзакции больше чем scope метода синхронизации. Когда вы вышли из метода, Spring начинает процесс флаша, закрытие сессии и коммит транзакции. Но в это время блокировка synchronized уже освобождена, поэтому второй поток читает старые данные из БД. Он коммита ещё не видит.

В случае с flush - запись в БД происходит во время flush. А при выходе из synchronized коммит происходит раньше чем чтение данных вторым потоком. Поэтому второй поток уже видит обновленные данные.

Нужно понимать что ни в том, ни в другом случае гарантий у вас нет. И если вдруг ресурсы компьютера будут заняты, то порядок действий может поменяться, как и результат работы вашего кода.
...
Рейтинг: 0 / 0
20.03.2017, 18:01
    #39423461
justcoder
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate+Spring transactions. Не понимаю некоторые вещи.
Но тогда получается что транзакция не комитится сразу после завершения метода , который помечен аннотацией @transactional.
авторSpring её закомитит при завершении того метода, в котором он её начал.

Код: java
1.
2.
3.
@Transactional
void transactionalMethodNoFlush(){
}



В однопоточной среде

Код: java
1.
2.
3.
4.
for(int i=0;i<100;i++){
transactionalMethodNoFlush();
//вот тут транзакция закомитилась
}



В многопоточной, используем тот же объект
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
Поток 1
.
.
transactionalMethodNoFlush();
.
.
Поток 2
.
.//Я ожидал что тут будут данные которые закомичены первым вызовом, а по факту нет
transactionalMethodNoFlush();
.
.
//Камит от первого потока
//Камит от второго потока
...
Рейтинг: 0 / 0
20.03.2017, 18:05
    #39423469
justcoder
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate+Spring transactions. Не понимаю некоторые вещи.
BlazkowiczРазница в том что scope транзакции больше чем scope метода синхронизации. Когда вы вышли из метода, Spring начинает процесс флаша, закрытие сессии и коммит транзакции. Но в это время блокировка synchronized уже освобождена, поэтому второй поток читает старые данные из БД. Он коммита ещё не видит.

В случае с flush - запись в БД происходит во время flush. А при выходе из synchronized коммит происходит раньше чем чтение данных вторым потоком. Поэтому второй поток уже видит обновленные данные.

Нужно понимать что ни в том, ни в другом случае гарантий у вас нет. И если вдруг ресурсы компьютера будут заняты, то порядок действий может поменяться, как и результат работы вашего кода.

Вот, да. Я просто думал что транзакция будет закомичена после выхода из метода.
Тогда что ж получается,
В однопоточной среде

Код: java
1.
2.
3.
4.
for(int i=0;i<100;i++){
transactionalMethodNoFlush();
//вот тут транзакция закомитилась
}



Что даже здесь нет гарантий, что при последующем проходе метод не увидит свежих данных, так что ли?
...
Рейтинг: 0 / 0
20.03.2017, 18:15
    #39423484
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate+Spring transactions. Не понимаю некоторые вещи.
justcoderНо тогда получается что транзакция не комитится сразу после завершения метода , который помечен аннотацией
Ну, потому что "сразу" это субъективная характеристика.
Для того чтобы закрыть транзакцию, нужно закрыть сессию (entitymanager aka hibernate session aka unit of work aka level 1 cache). Для того чтобы закрыть сессию, нужно её состояние сохранить в БД. То есть вызвать flush.
Всё это происходит "сразу" после выхода из метода. Но ещё раньше происходит высвобождение блокировки synchronized.
Если увеличить scope synchronized и вынести его за пределы метода, то, вероятно, эффект от flush-а исчезнет.
Код: java
1.
2.
3.
synchronized(carAuctionService) {
  carAuctionService.increasePriceProblemSynchronizedFlush(1, sleep);
}


Потому что транзакция будет внутри блокировки, а не снаружи.


justcoder.//Я ожидал что тут будут данные которые закомичены первым вызовом, а по факту нет
из описания процесса у вас потерялся synchronized.

Ваш поток выглядит так
1. начали транзакцию
2. захватили лок
3. вычитали данные
4. освободили лок
5. закомитили транзакцию.

"вычитали данные" может произойти в любой момент после 4. То есть , это может быть до 5 - когда коммит не произошел, а может быть после 5 - когда коммит произошел. Поэтому значение которое вы вычитали вышей синхронизацией не определяется.
Наличие\отсутствие flush влияет на только сколько времени просиходит между 4 и 5. Поэтому второй поток в зависимости от flush, то успевает, то не успевает. Но если у вас будет достаточно быстрая система, второй поток может всегда не успевать. А если достаточно медленная, то всегда успевать, и не важно будет там flush или нет.
...
Рейтинг: 0 / 0
20.03.2017, 18:16
    #39423485
Blazkowicz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate+Spring transactions. Не понимаю некоторые вещи.
justcoderдумал что транзакция будет закомичена после выхода из метода.
Ну, так и есть. Где у вас получилось иначе?

justcoderЧто даже здесь нет гарантий, что при последующем проходе метод не увидит свежих данных, так что ли?
Нет, не так.
...
Рейтинг: 0 / 0
20.03.2017, 18:29
    #39423498
justcoder
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate+Spring transactions. Не понимаю некоторые вещи.
Спасибо вам, теперь все стало понятно. В однопоточный среде (или в цикле) все работает так как положено потому что все идёт последовательно: вызов метода, закрытие сессии, вызов flush и т.д. И лишь потом снова в цикле вызовется транзакционный метод. Ну а так как все эти действия (Закрытие место , flush и т.д.) завершились то очередной вызов метода в цикле видит свежие закомиченные данные. Спасибо.
...
Рейтинг: 0 / 0
Форумы / Java [игнор отключен] [закрыт для гостей] / Hibernate+Spring transactions. Не понимаю некоторые вещи. / 12 сообщений из 12, страница 1 из 1
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]