MyBatis 配置中那些值得注意的坑

当你在使用Mybatis 时进行配置的时候有这样几个坑一定要注意下。

第一坑:Mybatis 的缓存机制

有时候你对数据库中的数据进行了增删改查操作,但是页面请求获取到的总不是最新的数据,这时候你就要检查是不是自己入坑了。

<setting name="cacheEnabled" value="true" />
<setting name="localCacheScope" value="SESSION" />

MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。
Tips:

关于缓存配置,第一个设置一般都会设置成false,这个坑很少会进,但是第二个默认的值为session,这个就是一个大坑,一不留神你就可能进坑,如果你设置成了session 那么就会有缓存,当你对数据进行了修改,但是你每次的请求获取数据都不是最新的,你发现页面还是原来数据,但是数据库已经修改了。这在项目开发和使用中可是相当蛋疼的问题。
解决方案:

<setting name="cacheEnabled" value="false" />
<setting name="localCacheScope" value="STATEMENT" />

第二坑:Mybatis数据库语句打印不出来

你有没有发现当你和struts2 或者springmvc 等其他框架整合后发现,想打印Mybatis 数据库语句却怎么样也打印不出来?

不少应用服务器的classpath中已经包含Commons Logging,如Tomcat和WebShpere,
所以MyBatis会把它作为具体的日志实现。记住这点非常重要。这将意味着,在诸如
WebSphere的环境中——WebSphere提供了Commons Logging的私有实现,你的Log4J配置将被忽略。这种做法不免让人悲催,MyBatis怎么能忽略你的配置呢?事实上,因Commons Logging已经存 在了,按照优先级顺序,Log4J自然就被忽略了!不过,如果你的应用部署在一个包含Commons Logging的环境, 而你又想用其他的日志框架,你可以通过在MyBatis的配置文件mybatis-config.xml里面添加一项setting(配置)来选择一个不同的日志实现。

官方提供的解决方案:
http://www.mybatis.org/mybatis-3/zh/logging.html

<configuration>
  <settings>
    ...
    <setting name="logImpl" value="LOG4J"/>
    ...
  settings>
configuration>

但是当你以为找到答案而欣喜时,按照官网这样配置后发现还是打印不出来SQL 语句?why?


<Configuration>
	<Appenders>
		<Console name="STDOUT" target="SYSTEM_OUT">
			<PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n" />
		Console>
		<File name="MyFile" fileName="${log4j:configParentLocation}/logs/${date:yyyy-MM-dd}-application.log">
			<PatternLayout>
				<Pattern>%d %p %c{1.} [%t] %m%nPattern>
			PatternLayout>
		File>
	Appenders>
	<Loggers>
		<Logger name="com.opensymphony.xwork2" level="info" />
		<Logger name="org.apache.struts2" level="info" />
		<Logger name="yourcompany.com" level="error" />
		<Root level="error">
			<AppenderRef ref="STDOUT" />
			<AppenderRef ref="MyFile" />
		Root>
	Loggers>
Configuration>

好吧,我就不卖关子了,当你等级设置成info 或者error的时候,你是看不到SQL的,想看到打印的SQL语句只需要将等级设置为debug 即可。

正确配置如图所示:


<Configuration>
	<Appenders>
		<Console name="STDOUT" target="SYSTEM_OUT">
			<PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n" />
		Console>
		<File name="MyFile" fileName="${log4j:configParentLocation}/logs/${date:yyyy-MM-dd}-application.log">
			<PatternLayout>
				<Pattern>%d %p %c{1.} [%t] %m%nPattern>
			PatternLayout>
		File>
	Appenders>
	<Loggers>
		<Logger name="com.opensymphony.xwork2" level="info" />
		<Logger name="org.apache.struts2" level="info" />
		<Logger name="yourcompany.com" level="debug" />
		<Root level="debug">
			<AppenderRef ref="STDOUT" />
			<AppenderRef ref="MyFile" />
		Root>
	Loggers>
Configuration>

第三坑 数据源

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

许多 MyBatis 的应用程序将会按示例中的例子来配置数据源。然而它并不是必须的。要知道为了方便使用延迟加载,数据源才是必须的。
有三种内建的数据源类型(也就是 type=”[UNPOOLED|POOLED|JNDI]”)

然而我们一般大多数都会选择POOLED 是不是?

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    dataSource>
  environment>
environments>

只是这样来看似乎并没有什么大的问题,但是你有没有发现当你的tomcat 运行了一段时间后会挂掉?

iBatis自己带了一个simple的数据库连接池,基本的功能都有。但是在处理部分数据库(比如mysql)的连接空闲时间太长(mysql是8小时)自动超时的时候,就比不上象c3p0这样的连接池软件了(c3p0能自动处理数据库连接被关闭的情况)。

感谢博主:http://www.blogjava.net/usherlight/archive/2008/05/13/200164.html

解决方案:

<dataSource type="POOLED">


<property name="poolMaximumActiveConnections" value="300" />

<property name="poolMaximumIdleConnections" value="0" />

<property name="poolMaximumCheckoutTime" value="20000" />

<property name="poolTimeToWait" value="20000" />


<property name="poolPingEnabled" value="true" />

<property name="poolPingConnectionsNotUsedFor" value="3600000" />
				
				<property name="poolPingQuery" value="select 1" />
			dataSource>
		

上面注意一个属性值,在任意时间可以存在的活动(也就是正在使用)连接数量
这个值我设置成了300是因为我查看了我的阿里云服务器最大连接数是600,但是本地安装的MySQL我看了下my.ini 文件,默认最大连接数是341。你可以根据自己实际情况修改。

<property name="poolMaximumActiveConnections" value="300" />

这里还要强调一点,这个值最好设置为0,否则会入坑。

<property name="poolMaximumIdleConnections" value="0" />

具体原因详情请移步:
使用Mybatis时请注意这两个参数,否则会让你的数据库连接爆掉
http://biancheng.dnbcw.net/mysql/420838.html

好了,坑总结完了。
现在献上自己的mybatis3 配置模板文件:



<configuration>

	
	<settings>
		
		<setting name="cacheEnabled" value="false" />
		
		<setting name="localCacheScope" value="STATEMENT" />
		
		<setting name="jdbcTypeForNull" value="NULL" />
		
		<setting name="logImpl" value="LOG4J2" />
	settings>

	
	<environments default="development">
		<environment id="development">
			
			<transactionManager type="JDBC" />
			
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
				<property name="url"
					value="jdbc:mysql://your ip:3306/your_db_name" />
				<property name="username" value="root" />
				<property name="password" value="test" />
				
				
				<property name="poolMaximumActiveConnections" value="300" />
				
				<property name="poolMaximumIdleConnections" value="0" />
				
				<property name="poolMaximumCheckoutTime" value="20000" />
				
				<property name="poolTimeToWait" value="20000" />

				
				<property name="poolPingEnabled" value="true" />
				
				<property name="poolPingConnectionsNotUsedFor" value="3600000" />
				
				<property name="poolPingQuery" value="select 1" />

			dataSource>
		environment>
		
		<environment id="Product_Intranet">
			
			<transactionManager type="JDBC" />
			
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
				<property name="url"
					value="jdbc:mysql://your ip:3306/your_db_name" />
				<property name="username" value="root" />
				<property name="password" value="test" />

				
				
				<property name="poolMaximumActiveConnections" value="300" />
				
				<property name="poolMaximumIdleConnections" value="0" />
				
				<property name="poolMaximumCheckoutTime" value="20000" />
				
				<property name="poolTimeToWait" value="20000" />

				
				<property name="poolPingEnabled" value="true" />
				
				<property name="poolPingConnectionsNotUsedFor" value="3600000" />
				
				<property name="poolPingQuery" value="select 1" />
			dataSource>
		environment>
	environments>

	
	<mappers>
		
		<mapper resource="xingyun/dao/mapper/ManagerMapper.xml" />
	mappers>
configuration>

第四坑:代码错误导致的缓存

  • 如果SqlSessionFactory 如果不是单例的,会容易产生大量的连接对象,导致挂掉。
  • 现在想想,我觉得当年的我可能有个误解,查询数据不是最新,而是缓存,并不是这个工具类的问题,我后来修正的方案虽然也能实现多线程下安全访问,但是加锁会减慢速度。
  • 凡是插入,更新,删除,甚至是查询,使用完session之后必须 调用closeSession方法关闭Session, 否则也会出现更新了数据,但是查询出来的不是最新的数据的问题。
  • 我之前一直错误以为只有插入,修改,删除操作后需要关闭session,查询可以直接 return session.getUserList(); 再次使用就不用再次打开一次session ,减少性能开销.但是忽略了多线程下
  • 试想下,多线程环境下,如果一个线程先执行一次查询,这个线程获取了一个session, 然后另外一个线程修改了数据,然后关闭了session.,再次查询由于这个session不为空,获取的还是旧的session,这样就会获取到的不是最新的数据而是缓存了。

我的那种方法就删了,MybatisJDBCUtil.java 最佳代码如下:

import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

/**
 * MyBatis 获取数据库连接工具类
 * */
public class MybatisJDBCUtil {
	private static final String resource = "mybatis-jdbc-config.xml";// SRC 根目录下必须有这个文件
	private static InputStream inputStream;
	private static SqlSessionFactory sqlSessionFactory;

	static {
		try {
			inputStream = Resources.getResourceAsStream(resource);
			sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			System.err.println("can't find "+resource);
		}
	}

	private static final ThreadLocal<SqlSession> THREAD_LOCAL = new ThreadLocal<SqlSession>();

	public static SqlSession currentSession() {
		SqlSession session = THREAD_LOCAL.get();
		
		if (session == null) {
			session = sqlSessionFactory.openSession();
			THREAD_LOCAL.set(session);
		}
		return session;
	}

	public static void closeSession() {
		SqlSession session = THREAD_LOCAL.get();
		THREAD_LOCAL.set(null);
		if (session != null) {
			session.close();
		}
	}
}

你可能感兴趣的:(#,MyBatis3)