Hibernate的使用(四)--疑难篇

一,本节内容

上一节搭建了基于spring+springmvc+hibernate的项目,现在看来其实也没有什么复杂的,但是当初我自己搭建的过程中可是吃了不少苦头,所以这一章将用来记录我在搭建的过程中所遇到的难点,记录下来,防止以后又踩坑了。。。

二,坑的组成

我遇到的坑主要有两个,1是在spring配置文件中设置hibernate的包扫描路径时老是报错;2是使用spring基于注解的事务失败,即出错了却不回滚。接下来我会分两个章节来分别介绍我遇到的问题,已及解决的方法。

三,hibernate设置包扫描路径的问题

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">    
	    <property name="dataSource" ref="dataSource"></property>
		<property name="configLocation"  value="classpath:config/hibernate.cfg.xml"></property>
        <!-- hibernate自动扫描 实体类 -->
        <property name="packagesToScan" value="com.entity"></property>
</bean>

(亲爱的读者能够找到上面配置的问题吗?)上面是我在spring的配置文件中创建sessionFactory,引入dataSource,然后指定了包扫描路径,一切看似很平常,但是一运行就说我dataSource注入失败,原因的话就是sessionFactory创建失败了,此时我的内心:喵喵喵?然后看控制台报错信息:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [config/spring/applicationContext.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'packagesToScan' of bean class [org.springframework.orm.hibernate3.LocalSessionFactoryBean]: Bean property 'packagesToScan' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?

说我的返回类型不匹配?这是个什么错误。好吧,在网上搜了半天,然后换了种形式:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">    
	    <property name="dataSource" ref="dataSource"></property>
		<property name="configLocation"  value="classpath:config/hibernate.cfg.xml"></property>
        <!-- hibernate自动扫描 实体类 -->
       <property name="packagesToScan">
         <list>
           <value>com.entity</value>
         </list>
       </property>
</bean>

看了看网上好多人是用的这种方式添加的包扫描路径,那结果呢?结果肯定不容乐观,依旧抛出了上述的错误,然后我就奇了怪了,别人都是这样写的咋没问题啊;项目还没开始做,搭个环境就报一堆错,我顿时流下了不学无术的悔恨的泪水。

最后查了查网上发现另一种配置的方法:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">    
	    <property name="dataSource" ref="dataSource"></property>
		<property name="configLocation"  value="classpath:config/hibernate.cfg.xml"></property>
        <!-- hibernate自动扫描 实体类 -->
      <property name="mappingDirectoryLocations" value="/com/entity/"></property>
</bean>

然后直接看运行结果;

Caused by: java.lang.IllegalArgumentException: Mapping directory location [ServletContext resource [/com/entity/]] does not denote a directory

这tm的怎么不是目录了?当程序员真的好不容易啊。。。这都是啥bug。

后来,我又遇到了什么No such method的错误,然后网上搜,说是hibernate3什么的不支持packagesToScan属性,于是我赶紧把hibernate的包换成了5.几的,然后把此处的class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"换成了hibernate4,然而并没有什么作用,此时已经耗费了大半天,直到我看到了以下两个东西,这才找到了解决问题的方向:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">    

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

上面两种都是用来配置sessionFactory的,但是一个使用了LocalSessionFactoryBean,另一个却使用了AnnotationSessionFactoryBean,这一对比才发现蹊跷的地方,后面那个不是注解的意思吗?网上一搜顿时恍然大悟。

上面的两种配置sessionFactory的方式,前者是用于配合传统的xml形式使用的,而后者是基于注解的方式使用的,它们各有各的属性,比如上面的那种mappingDirectoryLocations属性就是配合了localSessionFactory用来扫描映射类的xml文件的,它可不是用于扫描注解的。。。真是有了牛头不对马尾的感觉,原来搞了一半天是犯了这样的错误,也只能怪自己是第一次整合项目,以前都是使用现成的,谁知道有这么多讲究啊。

其实要想知道这两种初始化方式有什么不同,在eclipse中通过ctrl+鼠标左键就能点进去相应的类,可以看其具体的实现还有到底有哪些属性,然后就能够很容易的发现PackageToScan属性是人家那个注解的类的属性呀,另外需要注意的AnnotationSessionFactoryBean继承自localSessionFactory。好了,到此第一个问题解决了,虽然确实不难,但是要是不知道里面的问题,可能会和我一样困住大半天的啊;下面贴出正确的扫描方式

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<!-- 	<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">     -->
	    <property name="dataSource" ref="dataSource"></property>
		<property name="configLocation"  value="classpath:config/hibernate.cfg.xml"></property>
        <!-- hibernate自动扫描 实体类 ,下面这个扫描xml的啊-->
<!--       <property name="mappingDirectoryLocations" value="/com/entity/"></property> -->
         <property name="packagesToScan"> -->
          <list>
             <value>com.entity</value>
          </list>
       </property>
</bean>

四,spring基于注解的事务管理失败的问题

这个问题是我用于测试当service层调用dao层的方法产生异常后,finally语句中的数据库操作会不会被回滚呢?

首先先看我的测试代码:

package com.service;

import javax.annotation.Resource;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.dao.UserDao;
import com.entity.User;

@Service("userService")
@Transactional
public class UserService {
    
	private Logger LOG = Logger.getLogger(UserService.class);
	@Resource(name="userDao")
	private UserDao userDao;
	
	public User canLogin(String account,String password) {
		
		return this.userDao.canLogin(account, password);
	}
	
	public int userAdd(User user) {
      int result = 0;
	  try {
	        result = this.userDao.userAdd(user);
	        throw new Exception("这是测试的异常啊啊啊啊");
	  }catch(Exception e) {
		  LOG.info("自定义异常被捕获!");
		  throw new RuntimeException("手动抛出异常!");
	  }finally {
		 //记录添加用户时的时间信息 
		 this.userDao.userAddInfor(); 
	  }
	  
	}
}

上面的代码很简单,我在用户添加的时候,当添加成功之后抛出一个手动的异常,最后再把它抛出去;在finally代码块中调用了userAddInfo的方法,就是把当前的时间信息插入到数据库中的tb_message表中进行记录,然后我们看控制台输出:

信息: Starting ProtocolHandler ["http-bio-8080"]
Hibernate: 
    insert 
    into
        tb_user
        (account, age, name, password) 
    values
        (?, ?, ?, ?)
[INFO] [2019-01-01 20:16:01] com.service.UserService.userAdd(30) | 自定义异常被捕获!
Hibernate: 
    insert 
    into
        tb_message
        (message,time) 
    values
        ('用户开始添加了','2019-01-01 08:16:01')
一月 01, 2019 8:16:01 下午 org.apache.catalina.core.StandardWrapperValve invoke
严重: Servlet.service() for servlet [DispatherServlet] in context with path [/hello] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: 手动抛出异常!] with root cause
java.lang.RuntimeException: 手动抛出异常!

手动的异常被捕获,并且抛出了,之所以抛出运行时异常,是因为spring默认只会处理运行时异常和其子类。

然而奇怪的是数据库中一查表,喵喵喵。。。竟然两个表都插入成功了,怎么回事小老弟?不是说好的有异常抛出就会回滚的吗,怎么都成功了,代码诚不欺我啊=。=

然后就又陷入了网上查找资料的过程,主要找到这么几个解决方式:

(1)在代码中通过try,catch将异常捕获了,最后还要抛出,不然Spring容器无法进行事务管理。而且抛出的类型必须是RuntimeException,当然也可以在类上加上@Transaction(RollbackFor=Exception.class),这样所有的异常都会进行回滚

(2)有种说法就是,springmvc为子容器,它扫描了service层的注解并生成相应的bean,可是父容器Spring无法获取到相应的bean,所以在父容器上添加的事务自然就没有作用 ;解释就是父容器的bean,子容器可以看到,但反过来就不行了(这是网上查到的资料)解决方式就是springmvc专注只扫描controller注解,spring扫描除过controller的所有注解,配置如下:


<!--在springmvc的配置文件中,设置包扫描时,只扫描controller-->
<context:component-scan base-package="com.controller" >
        <context:include-filter type="annotation"
            expression="org.springframework.stereotype.Controller" />
    </context:component-scan>


<!--在spring的配置文件中,设置包扫描时,不扫描controller-->
 <context:component-scan base-package="com.*">
      <!-- 配置不扫描controller -->
      <context:exclude-filter type="annotation"
        expression="org.springframework.stereotype.Controller" />
   </context:component-scan>

上面这两种问题,我都没有遇到,那么我的问题是?前方高能,直接贴代码,看看,我dao层的实现:

public int userAdd(User user) {
		if(user!=null) {
			Session session = sessionFactory.openSession();
			session.save(user);
			return 1;
		}
		return 0;
	}

	public void userAddInfor() {
		String message = "用户开始添加了";
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
		String sql = "insert into tb_message(message,time) values ('"+message+"','"+ sdf.format(date)+"')";
		Session session = sessionFactory.openSession();
		SQLQuery query = session.createSQLQuery(sql);
		query.executeUpdate();
	}

眼尖的同学如果能一眼发现我的错误,那么恭喜你,你的理解比我深入多了。我的问题便是下面这行代码:

Session session = sessionFactory.openSession();

这行代码是用于开启session的,在之前没有使用框架的时候没有任何问题,所以我在整合框架的时候自然而然的使用该方法了,然而问题便在这里,sessionFactory.openSession()虽然会开启一个新的session,但是该session不是Spring容器维护的那个,自然就没有事务的功能了,正确的做法是使用sessionFactory.getCurrentSession()这样获取的session是与当前线程绑定的session是处于同一个事务,具有事务的功能,而且操作完成后不用关闭,spring容器会在事务提交的时候自动关闭;而openSession获取到的session资源是很有限的,所以需要使用完后进行关闭。

另外值得一提的是:

<property name="hibernateProperties"> 
 <props> 
   <prop key="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</prop>              
   <prop key="hibernate.show_sql">true</prop> 
   <prop key="hibernate.format_sql">true</prop> 
   <prop key="hibernate.current_session_context_class">thread</prop>
</props> 
</property> 

在hibernate的配置文件中,<prop key="hibernate.current_session_context_class">thread</prop>当你选择session绑定的上下文为thread时事务不会成功,此时去掉该配置就行了,在spring事务管理中,current Session是绑定到SpringSessionContext中的,而不是ThreadLocalSessionContext中的。

hibernate的该属性有三种选择,分别为:jta,thread,org.springframework.orm.hibernate3.SpringSessionContext,它们都有各自的不同用处,在spring中默认是最后一个。

五,拨云见日

找到了问题,接下来修改便很容易了,当我把session修改为currentSession后,运行结果又会怎么样呢?

最后会发现控制台虽然输出了插入语句,但是数据库中并没有插入数据,而是由于异常产生被回滚了,这也实现了spring事务管理的功能,另外finally中的数据库操作也会被回滚的呀,它们属于同一个事务。还有需要注意的是spring并不提供具体的事务管理,它只是提供了接口,而实现却是由别的数据库框架来实现的,在使用hibernate时我们使用的事务是:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">

常用的事务管理的方式有直接使用jdbc的事务管理,即成功了commit,失败了调rollback方法;也有就是异常发生了什么也不做的。

如果在代码中发生异常了自己又不想抛出去,此时就可以在代码里手动进行异常的回滚:

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 

六,其他

(1)spring事务

spring的事务分为声明式和编程式,声明式就是利用aop在事务开始前做什么,结束后做什么,需要配置切面或者在需要的事务的类上或方法上直接添加@Transactional注解;编程式就是在代码中进行设置,比如hibernate的手动开启事务,事务完成后commit等。

通过在类上添加@Transactional注解,这样该类的public方法就具有了事务的特性,注意在private方法上加是没有什么作用的,代码也不会报错;同时spring不建议把该注解直接加在接口上,这样做只有是基于代理的类才具有事务的属性;同时你既可以在类上添加注解,也可以在方法上添加注解,这样做,方法上的注解属性将会覆盖类上的。使用@Transactiona还可以设置隔离级别等属性。

声明式比编程式更常用,因为声明式不会侵入代码;编程式的好处是对事务的控制可以更为精细,在代码中进行设置。

(2)事务的注解标在dao层还是service层

配置事务的作用就是对事务进行管理,保证业务逻辑上数据的ACID,而Dao层是数据访问层,是不包含业务逻辑的,所以事务的管理就是用于保障Service层的;另外,一个service类中可能有多个dao的引用,所以注解加在service上也比较方便。

七,闲话篇

到此hibernate从最简单的使用,到整合spring就圆满成功了,当然这些只是个开始,很多地方都没有提到,但是从了解的角度来讲就够了,真到了项目上要使用时有了基础要拓展就不是什么大问题。我在这个过程中发现我遇到了问题就热衷于网上搜索答案,然而搜到的很多并不是自己就需要的,有些没有说到点子上,反倒是陷入更大的坑,所以闲了,把自己遇到的这些问题记录下来,还是挺好的,毕竟这都是自己遇到的曾经解决不了的坑,都是自己最大的收获啊,到此结束,希望有一天能够帮到别人。

ps:本人也是小白刚接触这一方面,所以很多地方可能说的有问题,甚至是有错误什么的,欢迎大家指出。我也会继续努力的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值