数据库连接泄漏是件可怕的事情,可能直接导致系统停止响应,另外因事务管理错误而导致数据出现不一致也是件可怕的事情,在后台系统中,这两个问题经常会结伴出现,本文将通过案例详解使用Spring+Hibernate时可能导致问题的几种情况,希望对大家有所帮助。
文章比较长,如果你是遇到了问题正急求解决方案的话,可以先只读这一条:检查你的dao是否有直接使用session,修改为使用hibernateTemplate。
如果你希望更多的了解session, hibernateTemplate, transaction,请继续。
以下案例基于Struts2.3.1+Spring3.1.1+hibernate3.5.4完成,案例场景为对系统参数进行管理,涉及如下四个类:
Action PropertyAction
Service PropertyService/PropertyServiceImpl
DAO PropertyDAO(继承HibernateDaoSupport)
Entity SysProperty
1. Action中直接调用dao,未使用getHibernateTemplate
在一个复杂的多层架构系统中,事务控制在service中完成是更合理的,不应交由view层来控制,本文假定你是这么做的。一般我们代码的处理顺序为action->service->dao,然而总会有人会打破这种逻辑,打破可以,但要注意如下问题。
假定Action中有如下代码:
1 @Autowired
2 private PropertyDAO propertyDAO;
3 @Override
4 public String execute() throws Exception {
5 model = propertyDAO.get(1L);
6 return null;
7 }
这里action直接访问了dao中的get方法,因事务配置在service层,因此这里是没有事务控制的。
接下来我们看dao中的get方法,假定你的定义如下:
1 public SysProperty get(Long id) {
2 return (SysProperty) getSession().get(SysProperty.class, id);
3 }
代码很简单,getSession是父类HibernateDaoSupport中提供的方法,这里直接通过实体Id get得到结果。
接下来发布一下系统,访问页面,好像一切OK,但是,再刷新几下看看,可能你的连接池配置的比较大,要刷新多次,最后你发会现你的系统停止了响应。更直接点,调用你采用的连接池中的相关API检测下当前连接占用情况,你会发现刷新一次,被占用连接增加一个,可怕的事情发生了。
小结:在无事务管理器控制的情况下,通过getSession()打开的session不会被关闭,这个session所占用的数据库连接也不会被释放。
接下来,来点更可怕的,假设你的action中是这样的:
1 private List<Long> ids;
2 @Override
3 public String execute() throws Exception {
4 for(Long id: ids) {
5 results.add(propertyDAO.get(id));
6 }
7 return null;
8 }
9
这时尝试一次传入多个id进行请求,请求结束后检查下连接数,你发现传入多少ID,就有多少连接被占用,也就是说每一次对dao的get调用都会占用一个不可释放的连接。
小结:如果没有配置事务管理器且直接使用getSession得到session,每次getSession都会创建一个新的session,每个session占用一个新的数据库连接,session无线程级别的共享。
2. Action中直接调用dao,使用getHibernateTemplate
当然,你可能没遇到过前面的情况,因为你会在DAO中这样写代码:
1 public SysProperty get(Long id) {
2 return getHibernateTemplate().get(getEntityClass(), id);
3 }
那么是否就一切OK呢?如果只是想解决连接泄漏这个问题的话,答案是Yes,尝试多次请求,检查下你的连接池状况,没有连接泄漏的情况出现。所以简单而可靠的办法就是不再直接使用getSession,而是使用getHibernateTemplate进行相应操作,当需要使用session时,可以用下面的方法:
getHibernateTemplate().execute(new HibernateCallback<T>() {
public T doInHibernate(Session session) throws HibernateException {
Query queryObject = session.createQuery(hql);
//....
}
});
But...如果你的系统真的出现了连接泄漏,可能你需要关注更多。
还是前面Action中根据ID循环查询的操作,在这个案例中,每一次Dao的get方法都将重复这样的逻辑:创建session,分配session一个连接,关闭session,释放session占用的连接,也就是说session不会在线程级别共享。让我们继续,进入第三种情况来进一步说明。
3. Service未合理配置事务管理器
绝大部分情况下我们会给service配置事务管理器,可能是通过xml或是@Transactional注解,但并非配置了就OK了。看下面的例子:
@Transactional
public class PropertyServiceImpl implements PropertyService {
@Autowired
private PropertyDAO propertyDAO;
@PostConstruct
public void init(){
propertyDAO.get(1L);
}
//
}
当然,假定你的DAO还是写成了这样:
public SysProperty get(Long id) {
return (SysProperty) getSession().get(SysProperty.class, id);
}
你期望在service初始化好后做一些数据库操作,你也给service配置了事务管理器,接下来你启动应用,然后检查连接池中的连接数,你会发现有连接未被释放!可能你会果断的修改dao中的方法:
public SysProperty get(Long id) {
return getHibernateTemplate().get(getEntityClass(), id);
}
然后你会理所当然的认为,即使配置了事务管理器,依然不能使用getSession(),事实可能并非如此。
我们调整一下代码,不在init中调用dao中的get方法,改为如下:
public SysProperty getProperty(Long id) {
return propertyDAO.get(id);
}
然后DAO继续使用(SysProperty) getSession().get(SysProperty.class, id),而action中的调用修改为:
@Autowired
private PropertyService propertyService;
@Override
public String execute() throws Exception {
model = propertyService.getProperty(1L);
return null;
}
重新发布,调用,检查连接数,发现无被占用连接存在。
小结:如果正确配置了事务管理器,getSession是安全的。
这时要清楚spring是通过AOP来实现事务控制的,而@PostConstruct方法不会受AOP控制,因此上面的init方法等于无事务管理器。
那么再回头来说,是否只要dao中使用getHibernateTemplate就不会有问题呢?
假定service中的@PostConstruct方法如下:
@PostConstruct
public void init(){
SysProperty p = new SysProperty();
p.setName("test_property_1");
propertyDAO.save(p);
}
强调一下,前面已经提到这个方法不受spring的事务管理器控制。
假定DAO中的save方法如下:
public void save(SysProperty o){
getSession().save(o);
}
启动一下应用,检查一下连接数,再检查一下数据是否有存储到数据库中。因为我们直接使用了getSession,因此连接不会释放,这点前面已经提到,但同时我们还将发现数据没有被存储,当然这个也好理解,因为上面已经提到这个方法未配置事务管理器。
小结:通过getSession().save()保存数据时,事务不会自动提交。
现在再修改下DAO中的save方法:
public void save(SysProperty o){
getHibernateTemplate().save(o);
}
启动一下应用,检查一下连接数,再检查一下数据是否有存储到数据库中。因为我们使用hibernateTemplate,因此连接有释放,这点前面已经提到,但同时我们还发现数据也已存储到数据库中,说明hibernateTemplate会自动提交事务。
小结:如果未配置事务管理器,通过hibernateTemplate操作时,会自动创建并提交事务。
所以如果你觉得使用hibernateTemplate就OK了,那就要小心下面的代码了:
@PostConstruct
public void init(){
//1. 从你的账号A中扣除一万块
//2. 这里的代码抛出了异常
//3. 将你的账号B中增加一万块
}
如果上面的第2步出现了异常,那么因为1的事务已经提交,而3却没有执行,最终导致了数据的不一致,后果和连接泄漏一样严重!
除了@PostConstruct,还有其它原因会导致@Transactional无效,假定我们的service配置了事务管理器,但存在如下代码:
pubic void someServiceMethod(){
new Thread(){
public void run(){
doSomethingLater();
}
}
}
public void doSomethingLater(){
//做一系列数据库操作
}
那么你可以去验证下doSomethingLater是否受事务管理器控制,事实上并不会,所以你需要理解spring AOP的机制,否则一个小坑会酿成灾难。
这里还有一种情况,你不是在类上面配置@Transactional,而是在方法上面配置,假定存在如下的代码:
//该方法不需要事务控制
public void method1(){
method2();
}
//下面的方法需要事务控制
@Transactional
public void method2(){
&nbs
在不对MyEclipse进行设置的时候,默认保存文件的编码,一般跟简体中文操作系统(如windows2000,windowsXP)的编码一致,即GBK。
在简体中文系统下,ANSI 编码代表 GBK编码;在日文操作系统下,ANSI 编码代表 JIS 编码。
Window-->Preferences-->General -->content Types
在右侧窗口中展开每一个子项,依次输入编码格式,如“UTF-8”点击“update”就设置好了。
例如:在以上设置中,设置jsp默认编码格式为“UTF-8”,那么以后新建的jsp文件,都是以“UTF-8”的格式保存的。
同样设置java默认编码格式为“UTF-8”,那么以后新建的java文件,都是以“UTF-8”的格式保存的。
设置html默认编码格式为“UTF-8”,那么以后新建的html文件,都是以“UTF-8”的格式保存的。
以上设置了文件的保存编码格式,默认的打开方式(解码方式)会跟保存编码格式一样。
二、设置新建其他文件的默认编码格式,即文件保存格式。
在第一项设置中,只是设置了常用文件类型的编码格式。如果新建一个文件,没有后缀名,也就是不指名文件类型,那么MyEclipse无法判断该用何种编码
格式保存,就用默认文件保存编码格式GBK进行保存。这个默认保存编码格式,可用如下方法修改:
window-->preferences-->general-->workspace 在右侧 Text file encoding ->Other 选择UTF-8
三、单个文件查看编码格式(也就是打开文件用的编码格式)更改。这个方法没有更改文件的编码格式,只是告诉MyEclipse用何种编码格式进行解码。
在要查看的文件上,如文件Test.java上右键-->Properties-->Resource在右侧Text file encoding ->Other,可以进行修改。
比如说,你在“一”里面设置*.java文件的编码格式为“UTF-8”,那么你打开后缀名为java的文件,默认就会以“UTF-8”格式进行解码。如果有人给了你一个
编写好的java文件,但是他是用GBK编码保存的,那么你现在打开的时候,会显示乱码。在这个文件上右键-->Properties-->Resource在右侧Text file
encoding ->Other ->GBK,就可以正常显示里面的内容了。
四、指定MyEclipse编写文件时页面内容数据编码格式,如html,或者jsp网页中提交表单前,用户输入的信息编码格式,配置文件xml中的参数编码格式等。
主要是通知浏览器或服务器,传输的数据的编码格式。
window-->Preferences-->MyEclipse-->Files and Editors-->选择子项-->Encoding
例如:选择UTF-8,
新建jsp文件的时候,contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"就会自动生成,不用每次去手工更改了。
新建html文件的时候,http-equiv="Content-Type" content="text/html; charset=UTF-8"
新建xml文件的时候,<?xml version="1.0" encoding="UTF-8"?>
以上是自己总结的,如果错误,请指出。问题如下
1、如果别人给了给的一个project,编码格式为GBK,我自己使用的编码格式为UTF-8,导入这个project后,显示乱码,需要进行哪些修改?
我想知道最有用的,必须进行的修改方法。如果我上面的几点依次全部更改,是可以正常显示的。但是更改以后,查看自己的代码又会出现中文乱码了。
其实这个问题就是,如何让两个Project使用不同编码(如UTF-8和GBK),而都可以正常查看,不出现乱码。
功能实现:分页,点击“加载更多”,将下一页的数据加载出来,页面不刷新。
用户entity类:
package com.test.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
@Entity
@Table(name="t_user",catalog="scott")
public class User implements Serializable{
private int user_id;
private String user_name;
private String user_sex;
private String user_class;
public User(){
}
public User(String user_name,String user_sex,String user_class){
this.user_name = user_name;
this.user_sex=user_sex;
this.user_class=user_class;
}
@Id
@Column(name="user_id")
@GeneratedValue(strategy=GenerationType.SEQUENCE,generator="user_seq")
@SequenceGenerator(sequenceName="user_seq",name="user_seq")
public int getUser_id() {
return user_id;
}
public void setUser_id(int user_id) {
this.user_id = user_id;
}
@Column(name="user_name",length=24)
public String getUser_name() {
return user_name;
}
public void setUser_name(String user_name) {
this.user_name = user_name;
}
@Column(name="user_sex",length=4)
public String getUser_sex() {
return user_sex;
}
public void setUser_sex(String user_sex) {
this.user_sex = user_sex;
}
@Column(name="user_class",length=24)
public String getUser_class() {
return user_class;
}
public void setUser_class(String user_class) {
this.user_class = user_class;
}
}
接口类:
package com.test.user.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.test.model.User;
@Service
public interface UserService {
/*******
* 分页加载
* @param pagesize
* @param pagenow
* @return
* @throws Exception
*/
public List<User> getAllUserByPage(int pagesize,int pagenow) throws Exception;
/*********
* 获取用户总记录数
* @return
*/
public int getAllUserCount();
/*************
* 创建用户
*
* @param user
* @throws Exception
*/
public void createUser(User user) throws Exception;
}
实现上面接口
package com.test.user.service.impl;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
import com.test.model.User;
import com.test.user.dao.UserDao;
import com.test.user.service.UserService;
@Component
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao;
/*******
* 分页加载
* @param pagesize
* @param pagenow
* @return
* @throws Exception
*/
public List<User> getAllUserByPage(int pagesize,int pagenow) throws Exception{
return this.userDao.getAllUserByPage(pagesize, pagenow);
}
/*********
* 获取用户总记录数
* @return
*/
public int getAllUserCount(){
return this.userDao.getAllUserCount();
}
@Override
public void createUser(User user) throws Exception {
this.userDao.createUser(user);
}
}
Dao底层操作类:
package com.test.user.dao;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Repository;
import com.test.model.User;
@Repository
public class UserDao extends HibernateDaoSupport {
private JdbcTemplate jdbcTemplate;
@Resource
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Resource
public void setSessionFactoryOverride(SessionFactory sessionFactory) {
super.setSessionFactory(sessionFactory);
}
/*******
* 分页加载
* @param pagesize
* @param pagenow
* @return
* @throws Exception
*/
public List<User> getAllUserByPage(int pagesize,int pagenow) throws Exception{
Query q=this.getSession().createQuery("from User");
q.setFirstResult((pagenow-1)*pagesize);
q.setMaxResults(pagesize);
List li=q.list();
return li;
}
/*********
* 获取用户总记录数
* @return
*/
public int getAllUserCount(){
int count=0;
Query q=this.getSession().createQuery("select count(*) from User");
count=Integer.parseInt(q.uniqueResult().toString());
return count;
}
public void createUser(User user) throws Exception {
this.getHibernateTemplate().save(user);
}
public List<User> getAllUser() throws Exception {
// return this.getHibernateTemplate().find("from User");
// 用jdbcTemplate查询
List<User> list = new ArrayList<User>();
SqlRowSet rs = this.jdbcTemplate
.queryForRowSet("select user_id,user_name,user_sex,user_class from t_user");
while (rs.next()) {
User user = new User();
user.setUser_id(rs.getInt("user_id"));
user.setUser_name(rs.getString("user_name"));
user.setUser_sex(rs.getString("user_sex"));
user.setUser_class(rs.getString("user_class"));
list.add(user);
}
return list;
}
}
控制器Action类:
package com.test.user.action;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import net.sf.json.JSONArray;
import org.apache.struts2.ServletActionContext;
import org.springframework.stereotype.Controller;
import com.opensymphony.xwork2.ActionSupport;
import com.test.model.User;
import com.test.user.service.UserService;
@Controller
public class UserAction extends ActionSupport {
@Resource
private UserService userService;
private List<User> userList = new ArrayList<User>();
public List<User> getUserList() {
return userList;
}
public void setUserList(List<User> userList) {
this.userList = userList;
}
/************************分页**************************/
private int pagesize;//每页显示多少条
private int currentPage;//当前页
public int getPagesize() {
return pagesize;
}
public void setPagesize(int pagesize) {
this.pagesize = pagesize;
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
/*************
* 初始化加载用户数据
* @return
*/
public String getAllUserInit(){
try {
this.pagesize=7;//先加载 7 条数据
this.currentPage=1;//初始时设置当前页为1
this.userList=this.userService.getAllUserByPage(this.pagesize, this.currentPage);
} catch (Exception e) {
e.printStackTrace();
}
return "all";
}
/***********
* 分页-异步加载
* @return
*/
public String getAllUserByPage(){
HttpServletResponse response = ServletActionContext.getResponse();
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
//response.setHeader("Cache-Control", "no-cache");
try {
this.pagesize=7;//每次加载7条数据
System.out.println("当前页"+this.currentPage);
this.userList=this.userService.getAllUserByPage(this.pagesize, this.currentPage);
System.out.println("加载数据:"+this.userList.size());
PrintWriter out = response.getWriter();
JSONArray json=JSONArray.fromObject(this.userList);
out.print(json);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
spring配置文件applicationContext-datasource.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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass"><value>oracle.jdbc.driver.OracleDriver</value></property>
<property name="jdbcUrl"><value>jdbc:oracle:t