hibernate(四)之乐观锁与悲观锁

hibernate四之乐观锁与悲观锁

一、Hibernate锁机制

Hibernate提供了乐观锁和悲观锁机制,主要用于解决事务并发问题。

悲观锁:Hibernate认为任何操作都可能发生并发,因此在第一个线程查询数据时,就把该条记录锁住。此时其他线程对该记录不能做任何操作(即增删改操作都不能)。必须等当前线程事务结束才可以进行操作。

悲观锁的实现原理 Hibernate悲观锁机制实际上是采用数据库的锁机制实现。 数据库中SQL语句最后加for update则把记录直接锁死,其他用户增删改查都不行,只能等待:select * from TRAIN where id=1 for update; Hibernate中load重载方法:

session.load(Train.class,1,LockOptions.UPGRADE);

只能等待当前用户提交或回滚,若等待超时则报异常! 悲观锁的缺点:处理效率很低。

悲观锁使用步骤及测试

CREATE TABLE TRAIN(
ID NUMBER PRIMARY KEY,	T_START VARCHAR2(20),
T_END VARCHAR2(20),T_TICKET NUMBER);

INSERT INTO TRAIN VALUES(1,'beijing','shanghai',100);

创建实体

@Entity
public class Train {
    @GeneratedValue
    @Id
    private int id;
    private String name;
    private int ticket;

创建两个线程

public class ThreadClient extends Thread {//继承Thread
    public void run(){//模拟购票操作
        Session session=HibernateUtil.getSession();
        Transaction tx=session.beginTransaction();
        //Train train=(Train)session.load(Train.class, 1);//不加锁时,查询出火车信息
        Train train=(Train)session.load(Train.class,1,LockMode.UPGRADE);//设置悲观锁
        if(train.getTicket()>=1){//判断剩余票数>=购买票数
            try {//满足购票条件,进行购票操作
                Thread.sleep(2000);//模拟用户操作
            } catch (InterruptedException e) { 	e.printStackTrace(); 	}
			
            int ticket=train.getTicket()-1;//将票数更新
            train.setTicket(ticket);		
            System.out.println("购票成功!");
        }else {  System.out.println("票数不足,购买失败!");	}
        tx.commit();//持久对象,提交就自动同步,相当于执行了update		 					session.close();										            }
}

测试类

public static void main(String[] args) {
	ThreadClient c1=new ThreadClient();		c1.start();
	ThreadClient c2=new ThreadClient();		c2.start();
 /** 若ThreadClient类为无锁查询,则显示2个购买成功,但数据库却减少1张票。若ThreadClient类加了悲观锁,则显示2个购买成功,数据库减少2张票。**/}

乐观锁:认为发生并发几率非常小。相同的记录不同的用户都可以查询访问,当多个人都要修改该记录时,只有第一个提交的用户成功,其他的会抛出异常,提示失败!

乐观锁的实现原理

乐观锁机制是借助于一个“版本”字段实现,当第一个更新用户提交成功后,Hibernate会自动将该“版本”字段值+1,当其他用户提交,如果版本字段小于数据库中的值,则会抛出异常,提示失败。如果不使用框架技术,那么我们需要手工做对比,使用Hibernate框架后,Hibernate可以帮助我们做version对比的操作。

乐观锁使用步骤及测试 step1:向TRAIN表中添加一个VERSION版本字段

@Entity
public class Train {
    @GeneratedValue
    @Id
    private int id;
    private String name;
    private int ticket;
    @Version
    private int version;

将悲观锁中的ThreadClient类,改为不加锁的load方法

Train train=(Train)session.load(Train.class, 1);

执行结果:先提交的事务执行成功,后提交的事务执行失败,报错信息为:

StaleObjectStateException: Row was updated or deleted by another transaction

二、ssh整合

依赖

<!--spring -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.3.14.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.13</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>4.3.14.RELEASE</version>
</dependency>

<!--spring mvc-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>4.3.14.RELEASE</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.8.10</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.8.10</version>
</dependency>


<!--hibernate-->
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-core</artifactId>
  <version>4.2.0.Final</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-orm</artifactId>
  <version>4.3.14.RELEASE</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.44</version>
</dependency>

db.properties

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/sssp
jdbc.username=root
jdbc.password=root

web.xml

<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
	<servlet-name>springDispatcherServlet</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationMvc.xml</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>springDispatcherServlet</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>

<filter>
	<filter-name>CharacterEncodingFilter</filter-name>
	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
	<init-param>
		<param-name>encoding</param-name>
		<param-value>UTF-8</param-value>
	</init-param>
</filter>
<filter-mapping>
	<filter-name>CharacterEncodingFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

spring

<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-4.2.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
    <!-- 配置自动扫描的包 -->
    <context:component-scan base-package="com.hw.ssh">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:exclude-filter type="annotation"
                                expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>

    <!-- 配置数据源 -->
    <context:property-placeholder location="classpath:db.properties"/>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property>
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 设置Spring取那个包中查找相应的实体类 -->
        <property name="packagesToScan">
            <value>com.**.entity</value>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext</prop>
            </props>
        </property>
    </bean>

    <!-- 配置事务管理器 -->
    <bean name="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"></property>
    </bean>

  <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
</beans>

springmvc

<?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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-4.2.xsd
           http://www.springframework.org/schema/mvc
           http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd">


    <mvc:annotation-driven/>

    <!-- 静态资源 -->
    <mvc:default-servlet-handler/>

    <!-- 组件扫描 -->
    <context:component-scan base-package="com.hw.ssh.controller">
        <context:include-filter type="annotation"
                                expression="org.springframework.stereotype.Controller"/>
        <context:include-filter type="annotation"
                                expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>

    <!-- 视图解析 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="internalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

service

@Service
@Transactional(readOnly = true)//没有报Could not obtain transaction-synchronized Session for current thread
public class UserService {
    @Autowired
    private BaseDao<User> userDao;

    public List<User> list() {
        return userDao.find( "from User" );
    }

    @Transactional
    public void add(User user) {
        User user1 = userDao.get( "from User where id =1" );
        user1.setName( user.getName() );
        // userDao.update( user1 );不需要显示调用,@Transactional会持久化查出的对象
    }
}

通用dao

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;


public class BaseDao<T> {

    @Autowired
    private SessionFactory sessionFactory;

    private Class<T> clz;

    public Class<T> getClz() { 
        if (clz == null) { // 获取泛型的Class对象 
        clz = ((Class<T>) (((ParameterizedType) (this.getClass().getGenericSuperclass())) .getActualTypeArguments()[0])); 
        } 
        return clz; 
    }

    protected Session getCurrentSession() {
        return this.sessionFactory.getCurrentSession();
    }

    public Serializable save(T o) {
        return this.getCurrentSession().save(o);
    }

    public T get(Serializable id) {
        return (T) this.getCurrentSession().get(clz, id);
    }

    public T get(String hql) {
        Query q = this.getCurrentSession().createQuery(hql);
        List<T> l = q.list();
        if (l != null && l.size() > 0) {
            return l.get(0);
        }
        return null;
    }

    public T get(String hql, Map<String, Object> params) {
        Query q = this.getCurrentSession().createQuery(hql);
        if (params != null && !params.isEmpty()) {
            for (String key : params.keySet()) {
                q.setParameter(key, params.get(key));
            }
        }
        List<T> l = q.list();
        if (l != null && l.size() > 0) {
            return l.get(0);
        }
        return null;
    }

    public void delete(T o) {
        this.getCurrentSession().delete(o);
    }

    public void update(T o) {
        this.getCurrentSession().update(o);
    }

    public void saveOrUpdate(T o) {
        this.getCurrentSession().saveOrUpdate(o);
    }

    public List<T> find(String hql) {
        Query q = this.getCurrentSession().createQuery(hql);
        return q.list();
    }

    public List<T> find(String hql, Map<String, Object> params) {
        Query q = this.getCurrentSession().createQuery(hql);
        if (params != null && !params.isEmpty()) {
            for (String key : params.keySet()) {
                q.setParameter(key, params.get(key));
            }
        }
        return q.list();
    }

    public List<T> find(String hql, Map<String, Object> params, int page, int rows) {
        Query q = this.getCurrentSession().createQuery(hql);
        if (params != null && !params.isEmpty()) {
            for (String key : params.keySet()) {
                q.setParameter(key, params.get(key));
            }
        }
        return q.setFirstResult((page - 1) * rows).setMaxResults(rows).list();
    }

    public List<T> find(String hql, int page, int rows) {
        Query q = this.getCurrentSession().createQuery(hql);
        return q.setFirstResult((page - 1) * rows).setMaxResults(rows).list();
    }

    public Long count(String hql) {
        Query q = this.getCurrentSession().createQuery(hql);
        return (Long) q.uniqueResult();
    }

    public Long count(String hql, Map<String, Object> params) {
        Query q = this.getCurrentSession().createQuery(hql);
        if (params != null && !params.isEmpty()) {
            for (String key : params.keySet()) {
                q.setParameter(key, params.get(key));
            }
        }
        return (Long) q.uniqueResult();
    }

    public int executeHql(String hql) {
        Query q = this.getCurrentSession().createQuery(hql);
        return q.executeUpdate();
    }

    public int executeHql(String hql, Map<String, Object> params) {
        Query q = this.getCurrentSession().createQuery(hql);
        if (params != null && !params.isEmpty()) {
            for (String key : params.keySet()) {
                q.setParameter(key, params.get(key));
            }
        }
        return q.executeUpdate();
    }

}

参数map

Map<String, Object> map=new HashMap<>();
map.put("name", username);
User find = get("from User where name=:name",map);

当然也可以使用继承HibernateDaoSupport 就能获取HibernateDaoSupport里面的方法完成增删改查

@Service
public class ClassService extends HibernateDaoSupport{
	
    @Resource
    public void setSuperSessionFactory(SessionFactory sessionFactory) {
        this.setSessionFactory(sessionFactory);
    }	
	
    public void add(Classroom classroom) {		
        getHibernateTemplate().save(classroom);		
    }

}

sessionFactory.getCurrentSession();

  1. openSession 从字面上可以看得出来,是打开一个新的session对象,而且每次使用都是打开一个新的session,假如连续使用多次,则获得的session不是同一个对象,并且使用完需要调用close方法关闭session。

  2. getCurrentSession ,从字面上可以看得出来,是获取当前上下文一个session对象,当第一次使用此方法时,会自动产生一个session对象,并且连续使用多次时,得到的session都是同一个对象,这就是与openSession的区别之一,简单而言,getCurrentSession 就是:如果有已经使用的,用旧的,如果没有,建新的。

注意 :在实际开发中,往往使用getCurrentSession多,因为一般是处理同一个事务(即是使用一个数据库的情况),所以在一般情况下比较少使用openSession或者说openSession是比较老旧的一套接口了;

上一篇:Hibernate的使用


下一篇:linux系统中vi命令操作大全