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

Код: 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
Hibernate+Spring transactions. Не понимаю некоторые вещи.
    #39423348
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Проблема понимания вызвана тем что вы намешали в кучу несколько разных проблем и пытаитесь их понять как "транзакцию". Хотя каждую из этих проблем надо понимать отдельно.
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
Hibernate+Spring transactions. Не понимаю некоторые вещи.
    #39423395
justcoder
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Спасибо, за ответ и все таки

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

Код: 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
Hibernate+Spring transactions. Не понимаю некоторые вещи.
    #39423429
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
justcoderМетоды то в обоих случаях из-за synchronized вызываются последовательно, значит результат должен быть в обоих случаях одинаковым, а именно 20 и 30 в таблице истории. И судя из
BlazkowiczSpring её закомитит при завершении того метода, в котором он её начал.
Перед вызовом метода, метод должен видеть результаты камита, но результаты то разные.
Потому как результат не связан с флашем. У вас два потока, две транзакции, два коммита. Если два коммита прошло, значит транзакции прошли. А что именно у вас там комитилось, сказать сложно, так как мы видим только часть кода.
Надо бы ещё понимать что EntityManager это тоже не аналог транзакции.
...
Рейтинг: 0 / 0
Hibernate+Spring transactions. Не понимаю некоторые вещи.
    #39423439
justcoder
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
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
Hibernate+Spring transactions. Не понимаю некоторые вещи.
    #39423447
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Потому что flush меняет порядок записи в базу.
В случае с flush вы сразу пытаетесь сохранить изменение в БД.
В случае без явного flush, запись в БД происходит позже - во время закрытие EntityManager-а и коммита транзакции.
Чтобы понять как именно влияет flush, стоит посмотреть порядок запросов в логе и сравнить.
...
Рейтинг: 0 / 0
Hibernate+Spring transactions. Не понимаю некоторые вещи.
    #39423453
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Разница в том что scope транзакции больше чем scope метода синхронизации. Когда вы вышли из метода, Spring начинает процесс флаша, закрытие сессии и коммит транзакции. Но в это время блокировка synchronized уже освобождена, поэтому второй поток читает старые данные из БД. Он коммита ещё не видит.

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

Нужно понимать что ни в том, ни в другом случае гарантий у вас нет. И если вдруг ресурсы компьютера будут заняты, то порядок действий может поменяться, как и результат работы вашего кода.
...
Рейтинг: 0 / 0
Hibernate+Spring transactions. Не понимаю некоторые вещи.
    #39423461
justcoder
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Но тогда получается что транзакция не комитится сразу после завершения метода , который помечен аннотацией @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
Hibernate+Spring transactions. Не понимаю некоторые вещи.
    #39423469
justcoder
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
BlazkowiczРазница в том что scope транзакции больше чем scope метода синхронизации. Когда вы вышли из метода, Spring начинает процесс флаша, закрытие сессии и коммит транзакции. Но в это время блокировка synchronized уже освобождена, поэтому второй поток читает старые данные из БД. Он коммита ещё не видит.

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

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

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

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



Что даже здесь нет гарантий, что при последующем проходе метод не увидит свежих данных, так что ли?
...
Рейтинг: 0 / 0
Hibernate+Spring transactions. Не понимаю некоторые вещи.
    #39423484
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
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
Hibernate+Spring transactions. Не понимаю некоторые вещи.
    #39423485
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
justcoderдумал что транзакция будет закомичена после выхода из метода.
Ну, так и есть. Где у вас получилось иначе?

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


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