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();
-
openSession 从字面上可以看得出来,是打开一个新的session对象,而且每次使用都是打开一个新的session,假如连续使用多次,则获得的session不是同一个对象,并且使用完需要调用close方法关闭session。
-
getCurrentSession ,从字面上可以看得出来,是获取当前上下文一个session对象,当第一次使用此方法时,会自动产生一个session对象,并且连续使用多次时,得到的session都是同一个对象,这就是与openSession的区别之一,简单而言,getCurrentSession 就是:如果有已经使用的,用旧的,如果没有,建新的。
注意 :在实际开发中,往往使用getCurrentSession多,因为一般是处理同一个事务(即是使用一个数据库的情况),所以在一般情况下比较少使用openSession或者说openSession是比较老旧的一套接口了;