Trying to deal with the work of isolation. Created a Spring-Boot application using Hibernate. I need to understand how database table locking works during a transaction.

To do this, I launch two threads in turn in the application, each of which accesses the Database and reads a row from the person table. First, the first thread starts a transaction, but does not complete it, but falls asleep for 5 seconds. At this time, the second thread starts its transaction and refers to the same table, as I understand this transaction must wait for the table to unlock, end the transaction of the first thread and start its own. But the program displays the following:

 Sleep 5 sec... Block Table Jonh (2) Jonh (1) 

Do I understand everything correctly? Should this work? I tried different methods, but it didn't work out (Help


Program code

 @SpringBootApplication @ImportResource("classpath*:applicationContext.xml") @EnableScheduling public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } 

applicationContext.xml:

 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mydb"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <bean id="hibernate5AnnotatedSessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:hibernate.cfg.xml"/> <property name="annotatedClasses"> <list> <value>Person</value> </list> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager"> <property name="sessionFactory" ref="hibernate5AnnotatedSessionFactory"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> </beans> 

hibernate.xml:

 <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="connection.url">jdbc:mysql://localhost:3306/mydb</property> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="show_sql">true</property> </session-factory> </hibernate-configuration> 

Person: (Model layer)

 @Entity @Table(name = "person") public class Person { private int id; private String name; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") public int getId() { return id; } @Column(name = "name") public String getName() { return name; } public void setName(String name) { this.name=name; } @Override public String toString() { return name; } } 

PersonServiceImpl (Service Layer):

 @Service(value = "personService") public class PersonServiceImpl implements PersonService { private PersonDAO personDAO; @Autowired(required = true) @Qualifier(value = "personDAO") public void setPersonDAO(PersonDAO personDAO) { this.personDAO= personDAO; } @Scheduled(fixedDelay = 60000) public void myMethod() { new Thread(new Runnable() { @Override public void run() { try { System.out.println(personDAO.getById(1).toString)+" (1)"; } catch (Exception e) { e.printStackTrace(); } } }).start(); // Пауза, чтобы первый поток успел обратится к таблице try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new Runnable() { @Override public void run() { try { System.out.println(personDAO.getById(1).toString+" (2)"); } catch (Exception e) { e.printStackTrace(); } } }).start(); } } 

PersonDAOImpl (DAO layer):

 @Repository(value = "personDAO") public class PersonDAOImplimplements PersonDAO { private SessionFactory sessionFactory; @Autowired(required = true) @Qualifier(value = "hibernate5AnnotatedSessionFactory") public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } boolean firstThread = true; @Override @Transactional(isolation = Isolation.SERIALIZABLE) public Person getById(int id) { Person person = this.sessionFactory.getCurrentSession().get(Person.class, new Integer(id)); if(this.firstThread) { this.firstThread=false; // Останавливаем поток. Как я понимаю никто не должен получить доступ к Таблице System.out.println("Sleep 5 sec... Block Table"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } return person; } } 
  • Transactions that only read data have no reason to block each other. - Sergey
  • isolation levels are not locks, they can ultimately be expressed in locks, but are not required to run them, they are only obliged to follow a set of rules. - etki
  • @Etki Yes, thanks, I misunderstood the role of isolation, I need LockOptions and LockMode . I'm trying to figure out a simple example in order to use blocking access to table data in the future. The point is that the data selected from the table should be unchanged until the entire transaction ends, which relies on this sample and adds a new row to the same table. If both transactions select the same data set, this will lead to errors. - Alexey

1 answer 1

It makes little sense to talk about the behavior of competitive requests at different levels of isolation in isolation from a specific DBMS. An abstract isolation level is a set of minimum requirements for the elimination of very specific anomalies. (By the way, it is the minimum requirements. For example, read uncommited that does not allow reading uncommitted changes is allowed). About how exactly this level should be implemented in the standard does not say. Therefore, each DBMS may have its own approach to meeting these requirements.

The serializable level requires that the result of executing queries in parallel does not differ from executing sequentially. Two reading requests do not interfere with each other and do not change the state of the system. Therefore, even if in a particular subd the isolation level of serializable is implemented through locks - it is extremely strange to use exclusive locks for this. Shared locks are used that allow parallel reading of this line, but block the change. The use of exclusive blocking for reading requests will provoke a too sharp failure of the performance of competitive requests without any special positive aspects.

And maybe nothing is blocked, even with changes. But it is normal to arrive with a transaction serialization error with the requirement to simply repeat the transaction from the beginning.