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; } }
LockOptionsandLockMode. 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