Mybatis深入之数据库连接池原理

最后更新于:2022-04-01 16:30:14

# Mybatis深入之数据库连接池原理 ### 简介 主要记录Mybatis数据库连接池实现原理、如何使用连接池来管理数据库连接的、连接池如何向外提供数据库连接、当外部调用使用完成之后是如何将数据库连接放回数据库连接池的。 ### 准备 有前面的相关文章的铺垫、这里就不再从Mybatis数据库相关信息的初始化以及何时创建一个真正的数据库连接并且向外提供使用的。这两方面的过程可以参见[Mybatis深入之DataSource实例化过程 ](http://blog.csdn.net/crave_shy/article/details/46584803)和[Mybatis深入之获取数据库连接 ](http://blog.csdn.net/crave_shy/article/details/46597239)两篇文章。 了解Mybatis数据库连接池如何配置 ~~~ <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> ~~~ - 标签`<dataSource type="POOLED">`指明使用Mybatis自带数据库连接池 - 标签`<transactionManager type="JDBC"/>`指明使用JDBC形式管理事务、参见[ Mybatis深入之事务管理 ](http://blog.csdn.net/crave_shy/article/details/46595391) ### 原理分析 当Mybatis初始化完成之后、根据上面的配置以及前面的文章知道、 环境中的DataSource实例是PooledDataSource、环境中的Transaction实例是JdbcTransaction。 [Mybatis深入之获取数据库连接 ](http://blog.csdn.net/crave_shy/article/details/46597239)中知道、当执行第一个sql语句时才会尝试获取数据库连接详细的可以看前面的文章、这里直接上获取数据库连接的关键代码: ~~~ public Connection getConnection() throws SQLException { return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection(); } ~~~ - 获取PooledConnection——popConnection(dataSource.getUsername(), dataSource.getPassword()) - 调用PooledConnection的getProxyConnection()方法返回真正连接的代理连接 当尝试获取数据库连接的时候会使用数据库连接池功能、详细获取数据库连接的代码[Mybatis深入之获取数据库连接 ](http://blog.csdn.net/crave_shy/article/details/46597239)有简单介绍。 在想要了解数据库连接池原理之前需要了解一个Mybatis用来存放数据库连接池状态的类:PoolState、PooledConnection以及PooledDataSource类属性以及之间的关系: ![关系图](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a858402b27d.jpg "") - PoolState数据库连接池状态类、其内部有标识数据库连接池状态的各个属性、重点是两个属性:`List<PooledConnection> idleConnections`用于存放空闲状态的数据库连接。`List<PooledConnection> activeConnections`用于存放活动状态的连接、也就是正在使用的数据库连接。 - PoolState内部持有一个DataSource引用、在PoolState被实例化时初始化、主要用于展示当前数据库连接的一些配置信息、比如用户名、密码等。 - PooledConnection内部持有一个PooledDataSource、同样在PooledConnection被构造时实例化PooledDataSource、其中有两个属性`private long createdTimestamp; private long lastUsedTimestamp;`用来标识当前PooledConnection的创建时间和最后使用时间、用来判断此连接是否过期。 - PooledDataSource 简单的线程安全的数据库连接池、对外提供数据库连接池功能。 - ### 获取PooledConnection 从上面可以知道程序中使用了数据库连接池之后、获取的数据库连接是从PooledDataSource中方法popConnection(String username, String password)获取的、获取代码: ~~~ private PooledConnection popConnection(String username, String password) throws SQLException { boolean countedWait = false; PooledConnection conn = null; long t = System.currentTimeMillis(); int localBadConnectionCount = 0; //恒成立、直到上面定义的PooledConnection被正确实例化或者程序异常中止 while (conn == null) { //synchronized (state)是保证PooledDataSource是一个线程安全的数据库连接池的原因。 synchronized (state) { //如果当前数据库连接池中有空闲状态的数据库连接、则直接取出一个作为当前方法执行结果返回。 if (state.idleConnections.size() > 0) { // Pool has available connection conn = state.idleConnections.remove(0); if (log.isDebugEnabled()) { log.debug("Checked out connection " + conn.getRealHashCode() + " from pool."); } } else { // Pool does not have available connection //如果当前活动状态的数据库连接未达到数据库连接池容纳的最大连接数创建一个并返回 if (state.activeConnections.size() < poolMaximumActiveConnections) { // Can create new connection //创建一个内部持有真正数据库连接的PooledConnection conn = new PooledConnection(dataSource.getConnection(), this); @SuppressWarnings("unused") //used in logging, if enabled //真正的数据库连接 Connection realConn = conn.getRealConnection(); if (log.isDebugEnabled()) { log.debug("Created connection " + conn.getRealHashCode() + "."); } } else { // Cannot create new connection //取出最先放入活动状态数据库连接集合的数据库连接 PooledConnection oldestActiveConnection = state.activeConnections.get(0); long longestCheckoutTime = oldestActiveConnection.getCheckoutTime(); //如果过期、则创建一个新的、并将过期的这个从集合中移除 if (longestCheckoutTime > poolMaximumCheckoutTime) { // Can claim overdue connection state.claimedOverdueConnectionCount++; state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime; state.accumulatedCheckoutTime += longestCheckoutTime; state.activeConnections.remove(oldestActiveConnection); if (!oldestActiveConnection.getRealConnection().getAutoCommit()) { oldestActiveConnection.getRealConnection().rollback(); } conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); oldestActiveConnection.invalidate(); if (log.isDebugEnabled()) { log.debug("Claimed overdue connection " + conn.getRealHashCode() + "."); } } else { // Must wait //线程等待 try { if (!countedWait) { state.hadToWaitCount++; countedWait = true; } if (log.isDebugEnabled()) { log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection."); } long wt = System.currentTimeMillis(); state.wait(poolTimeToWait); state.accumulatedWaitTime += System.currentTimeMillis() - wt; } catch (InterruptedException e) { break; } } } } //如果经过上述步骤之后数据库连接不为空、则将此连接添加到数据库连接池中并作为结果返回。 if (conn != null) { if (conn.isValid()) { if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password)); conn.setCheckoutTimestamp(System.currentTimeMillis()); conn.setLastUsedTimestamp(System.currentTimeMillis()); state.activeConnections.add(conn); state.requestCount++; state.accumulatedRequestTime += System.currentTimeMillis() - t; } else { if (log.isDebugEnabled()) { log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection."); } state.badConnectionCount++; localBadConnectionCount++; conn = null; if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Could not get a good connection to the database."); } throw new SQLException("PooledDataSource: Could not get a good connection to the database."); } } } } } if (conn == null) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); } throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); } return conn; } ~~~ 总结上述步骤 1. 先看是否有空闲(idle)状态下的PooledConnection对象,如果有,就直接返回一个可用的PooledConnection对象;否则进行第2步。 1. 查看活动状态的PooledConnection池activeConnections是否已满;如果没有满,则创建一个新的PooledConnection对象,然后放到activeConnections池中,然后返回此PooledConnection对象;否则进行第三步; 1. 看最先进入activeConnections池中的PooledConnection对象是否已经过期:如果已经过期,从activeConnections池中移除此对象,然后创建一个新的PooledConnection对象,添加到activeConnections中,然后将此对象返回;否则进行第4步。 1. 线程等待,循环2步 下面是从网络上摘抄的一个流程图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a858404c220.jpg "") ### 获取最终数据库连接 通过PooledConnection.getProxyConnection()来获取最终数据库连接。 ~~~ public Connection getProxyConnection() { return proxyConnection; } ~~~ 这里只要弄清楚proxyConnection是什么就行,在上面一节获取PooledDataSource时候的popConnection方法中知道PooledConnection是在这个方法里面创建的、调用的都是相同的PooledConnection构造方法: ~~~ public PooledConnection(Connection connection, PooledDataSource dataSource) { this.hashCode = connection.hashCode(); this.realConnection = connection; this.dataSource = dataSource; this.createdTimestamp = System.currentTimeMillis(); this.lastUsedTimestamp = System.currentTimeMillis(); this.valid = true; this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this); } ~~~ - 重点关注最后一行代码`this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);` - 这里是动态代理模式来使用当前类PooledConnection来代理Connection类。 - 关于动态代理设计模式相关的这里不是重点、可以看一下[Java设计模式之代理 ](http://blog.csdn.net/crave_shy/article/details/21000887) 所以最终返回的是真正的Connection的代理类PooledConnection。至于为什么要这样做、接着看。 ### 数据库连接使用完毕放回连接池 不使用数据库连接池时、正常使用数据库连接的情况下、当使用完毕之后我们就会调用其close()方法来关闭连接、避免资源浪费。但是当使用了数据库连接池之后、一个数据库连接被使用完之后就不再是调用其close方法关闭掉、而是应该将这个数据库连接放回连接池、那么我们就要拦截Connection.close()方法、将这个Connection放回连接池、而不是关闭。 使用动态代理的方式实现上述功能、PooledConnection实现了InvocationHandler接口、并提供了invoke方法的实现。当调用Connection的方法时会执行PooledConnection的invoke方法: ~~~ /* * Required for InvocationHandler implementation. * * @param proxy - not used * @param method - the method to be executed * @param args - the parameters to be passed to the method * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[]) */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) { dataSource.pushConnection(this); return null; } else { try { if (!Object.class.equals(method.getDeclaringClass())) { // issue #579 toString() should never fail // throw an SQLException instead of a Runtime checkConnection(); } return method.invoke(realConnection, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } } ~~~ - 拦截Connection执行方法、 - 如果是close方法、放回数据库连接池 - 如果不是、放掉、由Connection继续执行 ~~~ protected void pushConnection(PooledConnection conn) throws SQLException { synchronized (state) { state.activeConnections.remove(conn); if (conn.isValid()) { if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) { state.accumulatedCheckoutTime += conn.getCheckoutTime(); if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } //根据当前PooledConnection包含真正Connection重新创建一个PooledConnection并放到连接池中 PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this); state.idleConnections.add(newConn); newConn.setCreatedTimestamp(conn.getCreatedTimestamp()); newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp()); conn.invalidate(); if (log.isDebugEnabled()) { log.debug("Returned connection " + newConn.getRealHashCode() + " to pool."); } //立刻唤醒正在等待的线程、主要是前面从数据库连接池获取数据库连接时、如果没有现成可用数据库连接时、要等待、直到有可用的为止这个线程 state.notifyAll(); } else { state.accumulatedCheckoutTime += conn.getCheckoutTime(); if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } conn.getRealConnection().close(); if (log.isDebugEnabled()) { log.debug("Closed connection " + conn.getRealHashCode() + "."); } conn.invalidate(); } } else { if (log.isDebugEnabled()) { log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection."); } state.badConnectionCount++; } } } ~~~ 1. 如果当前需要关闭的数据库连接以失效、废弃不管PoolState的badConnectionCount自增1 1. 如果当前连接有效并且当前数据库连接池中空闲连接数没有达到数据库连接池连接最大数、并且此数据库连接是所期望放回数据库连接池的。重新根据当前PooledConnection中的真正数据库连接Connection创建一个新的PooledConnection并放回数据库连接池中。 1. 如果当前空闲连接数已达到数据库连接池容量最大值、或者不是所期望的数据库连接、关闭连接 到这里、数据库连接池原理就结束了。 ### 补充 Mybatis的数据库连接池简单、线程安全。当与spring结合的时候通常也可以使用第三方数据库连接池、将有关数据库连接、事务都交由spring去管理。 更多内容:[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/45825599)
';

Mybatis深入之获取数据库连接

最后更新于:2022-04-01 16:30:11

# Mybatis深入之获取数据库连接 ### 简介 主要记录Mybatis何时获取数据库连接以及获取数据库连接的过程。难点在于明白在使用Mybatis数据库连接池情况下的数据库连接的获取过程。 ### 何时获取数据库连接 Mybatis只有在真正执行sql操作的时候才会去获取数据库连接。至于如何验证: ### 不深入源码 简单来讲就是有意将数据库配置信息写成、在一个sql执行过程中看哪一步抛数据库连接异常。 ~~~ public static void main(String[] args) throws Exception { String mybatisConfigPath = "config/mybatis/mybatis.xml"; InputStream inputStream = Resources.getResourceAsStream(mybatisConfigPath); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); int count = (Integer)sqlSession.selectOne("org.alien.mybatis.samples.mapper.AuthorMapper.getAllAuthorsCount"); System.out.println(count); } ~~~ - 上面是一段Mybatis执行代码 - 我们可以将Mybatis连接数据库的信息有意写错 - 再DEBUG模式下一步一步调试看哪一步会抛异常 - 抛异常的那一步就是真正获取数据库连接的一步 异常信息: ![异常信息](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a8580400de5.jpg "") ### 深入源码 这里简单提一下、具体后面会有。最有迷惑性的是觉得在`openSession()`的时候会获取数据库连接、其实不然: openSession()最终只是返回一个操作数据库的会话、并不包含数据库连接,DefaultSqlSession(这个是Mybatis初始化的时候返回的一个SqlSession)中的方法: ~~~ private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } ~~~ - 主要装配DefaultSqlSession中执行Sql的Excutor、后面关于一个Sql完整的执行过程会有对其的详细分析 显然真正获取数据库连接的操作是在`sqlSession.selectOne("org.alien.mybatis.samples.mapper.AuthorMapper.getAllAuthorsCount");`进行的。 ### 获取数据库连接 在真正的获取数据库连接代码之前、还有许多为sql执行而生的代码、这里暂时忽略或者一些必要的说明、主要重心放在如何获取数据库连接。 书接上回、从前面执行sql代码开始: ~~~ int count = (Integer)sqlSession.selectOne("org.alien.mybatis.samples.mapper.AuthorMapper.getAllAuthorsCount"); ~~~ 下图是上面代码一系列方法调用过程: ![debug执行过程](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a8580419e01.jpg "") 经过一系列调用到SimpleExecutor——》doQuery(): ~~~ public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); //RoutingStatementHandler StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //MappedStatement、这一句是关键 stmt = prepareStatement(handler, ms.getStatementLog()); /* *参数: * stmt: PreparedStatementLogger * resultHandler: null */ return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } } ~~~ - 上面经过一系列跳转之后各个引用的实例我加了注释、有兴趣的可以自己跟一下、有时候可能要多跟几遍 - 关键代码`stmt=prepareStatement(handler,ms.getStatementLog());`、这一句根据方法名就能猜测、是根据Connection来获取执行Sql的PrepareStatement - 但是到现在为止我们都没有看到方法的参数中有关于数据库连接的、在此方法中 SimpleExecutor——》prepareStatement(); ~~~ private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; //获取数据库连接。statementLog:org.apache.ibatis.loggin.slf4j.Slf4Impl Connection connection = getConnection(statementLog); //获取执行Sql的Statement——PrepareStatementLogger //PrepareStatementLogger是PrepareStatement的代理、多了对使用Mybatis执行sql语句时记录sql语句的功能 stmt = handler.prepare(connection); //将执行Sql需要的参数设置到PrepareStatement中。 handler.parameterize(stmt); return stmt; } ~~~ 其他的不关注、这里只看数据库连接代码:`Connection connection = getConnection(statementLog);`BaseExecutor——》getConnection(): ~~~ protected Connection getConnection(Log statementLog) throws SQLException { //如果关于数据库连接的日志记录级别是DEBUG级别、则为获取的Connection进行代理、新增日志记录功能、这里不是重点。 if (statementLog.isDebugEnabled()) { return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; } } ~~~ - Mybatis中关于事务配置项的值是”JDBC”、所以从[Mybatis深入之事务管理 ](http://blog.csdn.net/crave_shy/article/details/46595391)知道这里的transaction其实是:JdbcTransaction - 最终到JdbcTransaction获取连接的方法中 JdbcTransaction——》openConnection() ~~~ protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug("Opening JDBC Connection"); } connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } setDesiredAutoCommit(autoCommmit); } ~~~ - 上面的dataSource从[ Mybatis深入之DataSource实例化过程 ](http://blog.csdn.net/crave_shy/article/details/46584803)知道当使用数据库连接池的时候实例化的是PooledDataSource PooledDataSource——》getConnection(): ~~~ public Connection getConnection() throws SQLException { /* * 为理解方便、将原来代码拆分如下: */ PooledConnection pooledConnection = popConnection(dataSource.getUsername(), dataSource.getPassword()); Connection connection = pooledConnection.getProxyConnection(); return connection ; //return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection(); } ~~~ - 从拆分代码看分两步 - 获取数据库真正连接 - 获取真正数据库连接的代理类作为最终返回结果、至于代理是做什么、后面继续 PooledDataSource——》popConnection(): ~~~ private PooledConnection popConnection(String username, String password) throws SQLException { boolean countedWait = false; PooledConnection conn = null; long t = System.currentTimeMillis(); int localBadConnectionCount = 0; while (conn == null) { synchronized (state) { if (state.idleConnections.size() > 0) { // Pool has available connection conn = state.idleConnections.remove(0); if (log.isDebugEnabled()) { log.debug("Checked out connection " + conn.getRealHashCode() + " from pool."); } } else { // Pool does not have available connection if (state.activeConnections.size() < poolMaximumActiveConnections) { // Can create new connection conn = new PooledConnection(dataSource.getConnection(), this); @SuppressWarnings("unused") //used in logging, if enabled Connection realConn = conn.getRealConnection(); if (log.isDebugEnabled()) { log.debug("Created connection " + conn.getRealHashCode() + "."); } } else { // Cannot create new connection PooledConnection oldestActiveConnection = state.activeConnections.get(0); long longestCheckoutTime = oldestActiveConnection.getCheckoutTime(); if (longestCheckoutTime > poolMaximumCheckoutTime) { // Can claim overdue connection state.claimedOverdueConnectionCount++; state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime; state.accumulatedCheckoutTime += longestCheckoutTime; state.activeConnections.remove(oldestActiveConnection); if (!oldestActiveConnection.getRealConnection().getAutoCommit()) { oldestActiveConnection.getRealConnection().rollback(); } conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); oldestActiveConnection.invalidate(); if (log.isDebugEnabled()) { log.debug("Claimed overdue connection " + conn.getRealHashCode() + "."); } } else { // Must wait try { if (!countedWait) { state.hadToWaitCount++; countedWait = true; } if (log.isDebugEnabled()) { log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection."); } long wt = System.currentTimeMillis(); state.wait(poolTimeToWait); state.accumulatedWaitTime += System.currentTimeMillis() - wt; } catch (InterruptedException e) { break; } } } } if (conn != null) { if (conn.isValid()) { if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password)); conn.setCheckoutTimestamp(System.currentTimeMillis()); conn.setLastUsedTimestamp(System.currentTimeMillis()); state.activeConnections.add(conn); state.requestCount++; state.accumulatedRequestTime += System.currentTimeMillis() - t; } else { if (log.isDebugEnabled()) { log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection."); } state.badConnectionCount++; localBadConnectionCount++; conn = null; if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Could not get a good connection to the database."); } throw new SQLException("PooledDataSource: Could not get a good connection to the database."); } } } } } if (conn == null) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); } throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); } return conn; } ~~~ - 先解释一下逻辑、再关注具体的数据库连接方法 1. 先看是否有空闲(idle)状态下的PooledConnection对象,如果有,就直接返回一个可用的PooledConnection对象;否则进行第2步。 2. 查看活动状态的PooledConnection池activeConnections是否已满;如果没有满,则创建一个新的PooledConnection对象,然后放到activeConnections池中,然后返回此PooledConnection对象;否则进行第三步; 3. 看最先进入activeConnections池中的PooledConnection对象是否已经过期:如果已经过期,从activeConnections池中移除此对象,然后创建一个新的PooledConnection对象,添加到activeConnections中,然后将此对象返回;否则进行第4步。 4. 线程等待,循环2步 具体的创建数据库连接代码`conn = new PooledConnection(dataSource.getConnection(), this);` - 上面代码中的dataSource为UnpooledDataSource、可以从[ Mybatis深入之DataSource实例化过程 ](http://blog.csdn.net/crave_shy/article/details/46584803)了解原因。 所以先要看UnpooledDataSource——getConnection() 经过一系列跳转到同类如下方法: ~~~ private Connection doGetConnection(Properties properties) throws SQLException { initializeDriver(); Connection connection = DriverManager.getConnection(url, properties); configureConnection(connection); return connection; } ~~~ - 上面我们可以看到很熟悉的加载驱动、获取数据库连接 ### 补充 其实关于数据库部分还有很多要写的、比如数据库连接池工作原理、数据库连接何时关闭。这里暂不准备一次将所有的东都放在一起。觉得分开点更容易说清楚理解起来不是那么费劲。 后面会有一篇Mybatis数据库连接池原理来分析它。 更多内容[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/23932803)
';

Mybatis深入之事务管理

最后更新于:2022-04-01 16:30:09

# Mybatis之事务管理 ### 简介 Mybatis的事务管理分为两种JdbcTransaction,ManagedTransaction。其中JdbcTransaction仅仅是对数据库连接Connection的一个包装、内部管理数据库事务还是调用Connection的提交、回滚等事务操作方法。ManagedTransaction更直接、什么也没有做。直接将事务交给外部容器管理。 ### Mybatis事务管理相关类结构图 类概览: ![类概览](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a85803ca136.jpg "") 类UML图(典型的简单工厂模式来创建Transaction): ![类UML图](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a85803dc606.jpg "") - Transaction 封装事务管理方法的接口 - TransactionFactory 抽象事务工厂生产方法 - JdbcTransactionFactory实现TransactionFactory、用于生产JdbcTransaction的工厂类 - ManagedTransactionFactory实现TransactionFactory、用于生产ManagedTransaction的工厂类 - JdbcTransaction实现Transaction、只是对事务进行了一层包装、实际调用数据库连接Connection的事务管理方法 - ManagedTransaction 实现Transaction没有对数据库连接做任何事务处理、交由外部容器管理 ### 源码事务 ### 事务配置 Mybatis中关于事务的配置是通过`<transaction type="xx"/>`来指定的。配置如下: ~~~ <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> ~~~ - type为”JDBC”时、使用JdbcTransaction管理事务。 - type为”managed”时、使用ManagedTransaction管理事务(也就是交由外部容器管理) [Mybatis深入之初始化过程 ](http://blog.csdn.net/crave_shy/article/details/46013493 "Mybatis深入之初始化过程")中知道配置文件如何解析的、其中关于事务方面的解析: ~~~ private void environmentsElement(XNode context) throws Exception { //只关注事务部分... TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); ... } ~~~ ~~~ private TransactionFactory transactionManagerElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a TransactionFactory."); } ~~~ ~~~ typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); ~~~ - 重点在于根据type类型判断实例化何种TransactionFactory - 前面已经知道Mybatis两种事务配置的方式、这里使用的jdbc类型的事务 - 上一篇分析DataSource实例化过程中有一段是关于根据DataSource的type来获取何种Factory的、这里原理同样 - 通过TypeAliasRegistry根据type=’JDBC’来获取TransactionFactory实现类JdbcTransactionFactory 关键在于JdbcTransactionFactory通过newInstance()使用无参构造函数时做了什么工作 ~~~ public class JdbcTransactionFactory implements TransactionFactory { public void setProperties(Properties props) { } public Transaction newTransaction(Connection conn) { return new JdbcTransaction(conn); } public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { return new JdbcTransaction(ds, level, autoCommit); } } ~~~ - JdbcTransactionFactory默认无参构造方法被调用 - setProperties没有做任何实质性处理 - 对比ManagedTransactionFactory不再贴代码 下面就是获取具有事务特性的数据库连接了 JdbcTransaction: ~~~ public Transaction newTransaction(Connection conn) { return new JdbcTransaction(conn); } ~~~ ManagedTransaction: ~~~ public Transaction newTransaction(Connection conn) { return new ManagedTransaction(conn, closeConnection); } ~~~ - 两者都是通过Connection来创建具体的实例 JdbcTransaction: ~~~ public class JdbcTransaction implements Transaction { private static final Log log = LogFactory.getLog(JdbcTransaction.class); protected Connection connection; protected DataSource dataSource; protected TransactionIsolationLevel level; protected boolean autoCommmit; public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) { dataSource = ds; level = desiredLevel; autoCommmit = desiredAutoCommit; } public JdbcTransaction(Connection connection) { this.connection = connection; } public Connection getConnection() throws SQLException { if (connection == null) { openConnection(); } return connection; } public void commit() throws SQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug("Committing JDBC Connection [" + connection + "]"); } connection.commit(); } } public void rollback() throws SQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug("Rolling back JDBC Connection [" + connection + "]"); } connection.rollback(); } } public void close() throws SQLException { if (connection != null) { resetAutoCommit(); if (log.isDebugEnabled()) { log.debug("Closing JDBC Connection [" + connection + "]"); } connection.close(); } } protected void setDesiredAutoCommit(boolean desiredAutoCommit) { try { if (connection.getAutoCommit() != desiredAutoCommit) { if (log.isDebugEnabled()) { log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]"); } connection.setAutoCommit(desiredAutoCommit); } } catch (SQLException e) { // Only a very poorly implemented driver would fail here, // and there's not much we can do about that. throw new TransactionException("Error configuring AutoCommit. " + "Your driver may not support getAutoCommit() or setAutoCommit(). " + "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e); } } protected void resetAutoCommit() { try { if (!connection.getAutoCommit()) { // MyBatis does not call commit/rollback on a connection if just selects were performed. // Some databases start transactions with select statements // and they mandate a commit/rollback before closing the connection. // A workaround is setting the autocommit to true before closing the connection. // Sybase throws an exception here. if (log.isDebugEnabled()) { log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]"); } connection.setAutoCommit(true); } } catch (SQLException e) { log.debug("Error resetting autocommit to true " + "before closing the connection. Cause: " + e); } } protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug("Opening JDBC Connection"); } connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } setDesiredAutoCommit(autoCommmit); } } ~~~ - 从源码中可知、JdbcTransaction如何管理事务的、如前面所说调用DataSource事务操作方法。 - 并且对select不进行事务控制 - 当使用DataSource创建数据库连接时、数据库的事务隔离级别使用DataSource默认的事务隔离级别 - 如需指定事务的隔离级别、必须手动创建JdbcTransaction(调用另一个构造函数) - 关于事务隔离级别会在补充中有 ManagedTransaction: ~~~ public class ManagedTransaction implements Transaction { private static final Log log = LogFactory.getLog(ManagedTransaction.class); private DataSource dataSource; private TransactionIsolationLevel level; private Connection connection; private boolean closeConnection; public ManagedTransaction(Connection connection, boolean closeConnection) { this.connection = connection; this.closeConnection = closeConnection; } public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) { this.dataSource = ds; this.level = level; this.closeConnection = closeConnection; } public Connection getConnection() throws SQLException { if (this.connection == null) { openConnection(); } return this.connection; } public void commit() throws SQLException { // Does nothing } public void rollback() throws SQLException { // Does nothing } public void close() throws SQLException { if (this.closeConnection && this.connection != null) { if (log.isDebugEnabled()) { log.debug("Closing JDBC Connection [" + this.connection + "]"); } this.connection.close(); } } protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug("Opening JDBC Connection"); } this.connection = this.dataSource.getConnection(); if (this.level != null) { this.connection.setTransactionIsolation(this.level.getLevel()); } } } ~~~ - 重点看一下`commit()``rollback()`方法,没有方法体。验证前面其关于事务的管理方式 到这里事务暂时告一段落、一般在使用时会与spring结合、将数据库连接、事务管理都交由spring管理。 ### 补充 数据库隔离级别: 先对不同隔离级别涉及到的名词解释: • 脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的. • 不可重复读: 对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了. • 幻读: 对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几 具体的隔离级别定义: READ UNCOMMITTED(读未提交数据) 允许事务读取未被其他事务提交的变更,脏读、不可重复读和幻读的问题都会出现 READ COMMITED(读已提交数据) 只允许事务读取已经被其他事务提交的变更,可以避免脏读,但不可重复读和幻读问题仍然会出现 REPEATABLE READ(可重复读) 确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读的问题依然存在 SERIALIZABLE(串行化) 确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,所有并发问题都可以避免,但性能十分低 Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE. Oracle 默认的事务隔离级别为: READ COMMITED Mysql 支持 4 中事务隔离级别. Mysql 默认的事务隔离级别为: REPEATABLE READ 更多内容:[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/45825599)
';

Mybatis深入之DataSource实例化过程

最后更新于:2022-04-01 16:30:07

# Mybatis深入之DataSource实例化过程 ### 简介 主要介绍Mybatis启动过程中DataSource实例化的过程、为后面解析一个完整SQL执行过程做个前章。 ### Mybatis中DataSource体系 ### MybatisDataSource整体简介 Mybatis中关于数据库的类都在`org.apache.ibatis.datasource`包中 ![DataSource体系](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a85803a27fa.jpg "") Mybatis配置文件中关于数据库的配置: ~~~ <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> ~~~ - 重点关注`<dataSource type="POOLED">`的type属性、其有三种取值: - POOLED:使用Mybatis自带的数据库连接池来管理数据库连接 - UNPOOLED:不使用任何数据库连接池来管理数据库连接 - JNDI:jndi形式使用数据库连接、主要用于项目正常使用的时候 类与类之间的关系: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a85803b30c3.jpg "") 每一条线都是一种关系、简单解释一下 1. PooledDataSource实现java.sql.DataSource接口 2. PooledDataSource内部持有一个DataSource引用 3. UnpooledDataSource实现java.sql.DataSource接口 4. PooledDataSource内部持有一个UnpooledDataSource引用 5.PooledDataSourceFactory无参构造方法体中将其父类UnpooledDataSourceFactory持有的引用DataSource实例化为PooledDataSource 6. PooledDataSourceFactory继承UnpooledDataSourceFactory 7. UnpooledDataSourceFactory无参构造方法将其持有的引用DataSource实例化为UnpooledDataSource 8. UnpooledDataSourceFactory持有一个DataSource引用、用于返回实例化好的DataSource。 ### Mybatis中DataSource实例化整体过程 这里以使用Mybatis自带的数据库连接池为例。也就是type为 “POOLED”类型的数据连接。 1. 根据配置文件中type的类型实例化具体的DataSourceFactory。这里是POOLED所以实例化的是PooledDataSourceFactory。 1. 通过PooledDataSourceFactory来获取DataSource具体实例:PooledDataSource 对于第一步更详细点的过程: - 在Mybatis初始化Configuration对象时、Configuration中属性TypeAliasRegistry同样被实例化、并且在Configuration的无参构造方法中对TypeAliasRegistry注册了许多常用的类(以键值对的形式保存在TypeAliasRegistry的属性`private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();`中、也包括TypeAliasRegistry无参构造方法注册的基本java类型。 - 通过配置文件中type指定的”POOLED”在TypeAliasRegistry中查找其对应的类:PooledDataSourceFactory - 调用其newInstance()实例化 - PooledDataSourceFactory继承自UnpooledDataSourceFactory、所以UnpooledDataSourceFactory先被实例化、 - UnpooledDataSourceFactory无参构造方法中实例化了其DataSource引用为UnpooledDataSource。 - 接着实例化PooledDataSourceFactory、其无参构造方法体将父类UnpooledDataSourceFactory持有的DataSource实例化为PooledDataSource。 - PooledDataSource实例化时初始化了一些关于数据库连接池的配置信息 - PooledDataSource的无参构造方法中将其持有的UnpooledDataSource实例化。 - UnpooledDataSource中关于数据库连接的属性值在实例化DataSourceFactory之后读取properties值设置到对应属性上。 ### 具体过程 从上一篇中知道Mybatis初始化过程是解析Mybatis配置文件并装配Configuration对象。从Mybatis基础使用中知道Mybatis数据库连接信息的配置是在environments标签中配置的: ~~~ <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> ~~~ 所以想要了解DataSource初始化过程可以从XMLConfigBuilder中的parse方法入手: ~~~ private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { environment = context.getStringAttribute("default"); } for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id"); if (isSpecifiedEnvironment(id)) { TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); } } } } ~~~ - 这里同样有关于数据库事务的配置、具体事务有关的后面再说 - 主要看如何实例化DataSource、同样从上面代码中我们知道只是将DataSource实例化了而没有进行任何操作、原因是只有具体执行某SQL语句的时候才会使用DataSource来获取数据库连接。 获取DataSource关键代码: ~~~ DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); ~~~ - 根据配置文件中dataSource标签中内容实例化DataSourceFactory - 通过DataSourceFactory获取DataSource 下面首先看如何根据dataSource标签内容实例化DataSourceFactory ~~~ private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { //获取数据库连接池类型: POOLED-使用Mybatis自带数据库连接池。UNPOOL-不使用数据库连接池。这里看POOLED的情况。 String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); } ~~~ - 上面一段代码关键点在于`resolveClass(type)` - 经过一系列的方法调用、最终返回结果的方法是:`TypeAliasRegistry` ~~~ @SuppressWarnings("unchecked") // throws class cast exception as well if types cannot be assigned public <T> Class<T> resolveAlias(String string) { try { if (string == null) return null; String key = string.toLowerCase(Locale.ENGLISH); // issue #748 Class<T> value; if (TYPE_ALIASES.containsKey(key)) { value = (Class<T>) TYPE_ALIASES.get(key); } else { value = (Class<T>) Resources.classForName(string); } return value; } catch (ClassNotFoundException e) { throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e); } } ~~~ - 这里重点在于TypeAliasRegistry是何时实例化的 - 上一篇初始化过程中知道Configuration中有一个私有变量`protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();` - 进一步看看Configuration实例化的时候其无参构造函数体就知道其缘由 ~~~ public Configuration() { typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); typeAliasRegistry.registerAlias("FIFO", FifoCache.class); typeAliasRegistry.registerAlias("LRU", LruCache.class); typeAliasRegistry.registerAlias("SOFT", SoftCache.class); typeAliasRegistry.registerAlias("WEAK", WeakCache.class); typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); languageRegistry.register(RawLanguageDriver.class); } ~~~ - 从上面可以看出TypeAliasRegistry在实例化之后并初始化了其内部私有变量`private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();`来保存一些Type alias(类型别名)供后面程序使用。 从上面代码`typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);`可以看出最后返回的是`PooledDataSourceFactory`。 ~~~ public class PooledDataSourceFactory extends UnpooledDataSourceFactory { public PooledDataSourceFactory() { this.dataSource = new PooledDataSource(); } } ~~~ - PooledDataSourceFactory继承UnpooledDataSourceFactory 其中UnpooledDataSourceFactory拥有一个DataSource的protected级别的属性`protected DataSource dataSource;`并且其构造函数: ~~~ public UnpooledDataSourceFactory() { this.dataSource = new UnpooledDataSource(); } ~~~ 对比PooledDataSourceFactory的构造函数: ~~~ public PooledDataSourceFactory() { this.dataSource = new PooledDataSource(); } ~~~ 知道最终UnpooledDataSourceFactory的`protected DataSource dataSource;`实例是:`PooledDataSource`。 到这里只要知道PooledDataSource是什么、那么返回的DataSource就是什么。 当然在这之前还需要一步、就是将配置文件中的数据库连接信息设置到最后生成的DataSourceFactory(在这里就是PooledDataSourceFactory)中去。这个过程中使用了一个Mybatis很长用的用于操作反射的封装类:MetaObject。提供了一些简便的获取、设置类等通过反射来操作类的方法、以后有时间专门看一眼。 下面的主要目标就是看PooledDataSource调用其无参构造方法时到底做了什么。 PooledDataSource是java.sql.DataSource的一个实现类、其属性与无参构造方法如下: ~~~ private static final Log log = LogFactory.getLog(PooledDataSource.class); private final PoolState state = new PoolState(this); private final UnpooledDataSource dataSource; // OPTIONAL CONFIGURATION FIELDS protected int poolMaximumActiveConnections = 10; protected int poolMaximumIdleConnections = 5; protected int poolMaximumCheckoutTime = 20000; protected int poolTimeToWait = 20000; protected String poolPingQuery = "NO PING QUERY SET"; protected boolean poolPingEnabled = false; protected int poolPingConnectionsNotUsedFor = 0; private int expectedConnectionTypeCode; public PooledDataSource() { dataSource = new UnpooledDataSource(); } ~~~ - 设置了一些作为数据库连接池初始化使用的参数 - 无参方法体中实例话了属性`dataSource = new UnpooledDataSource();` - UnpooledDataSource无参构造函数是空方法题 - 要注意的是:前面实例化DataSourceFactory的时候最后一步是设置属性。其实就是通过MetaObject来将属性值设置到UnpooledDataSource的数据库连接属性上了。 到这里、关于数据库DataSource类的实例话也就结束了。 ### 补充 ### 对数据库实例化做个总结: 当使用数据库连接池时、即`<dataSource type="POOLED">`时、DataSourceFactory具体实例是PooledDataSourceFactory。返回的DataSource具体实例是内部持有UnpooledDataSource实例的PooledDataSource。 当不使用数据库连接池时、即`<dataSource type="UNPOOLED">` 时、DataSourceFactory具体实例是UnpooledDataSourceFactory。返回的DataSource具体实例是UnpooledDataSource实例。 更多内容:[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/45825599)
';

Mybatis使用之SpringMVC整合

最后更新于:2022-04-01 16:30:05

# Mybatis使用之SpringMVC整合 ### 一:简介      主要记录Mybatis如何与SpringMVC框架整合。Mybatis真正与Spring结合更能体现出其灵活、便捷的特点。数据源交由Spring管理、事务交由Spring管理、配置文件、映射文件交由Spring加载、初始化。映射接口交由Spring注入等等。大大简化了Mybatis代码、配置量。Mybatis只需关心与数据库打交道、处理数据与实体类之间的映射即可。 ### 二:环境介绍           主要是Mybatis3.2.8与Spring4.1.0之间的整合。项目是使用Maven搭建的项目。搭建步骤:      2.1 创建Maven项目      2.2 加入Mybatis+SpringMVC所需jar包依赖      2.3 配置Mybatis配置文件      2.4 配置Spring配置文件      2.5 配置SpringMVC配置文件      2.6 配置web.xml文件      2.7 配置Mybatis映射文件      2.8 编写与Mybatis映射文件对应的映射接口      2.9 配置日志资源文件      2.10 完成其他java代码编写、测试 ### 三:具体步骤及说明 ###           3.1 创建Maven项目           这里使用的IntellijIDEA 14、集成Maven插件、其他的IDE如Eclipse只需安装一下插件就行。 ###           3.2 加入Mybatis+SpringMVC所需jar包依赖                 具体如下面pom.xml文件所列:      ~~~ <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.star.coding.passion</groupId> <artifactId>star-coding-passion</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <java-version>1.7</java-version> <org.springframework-version>4.1.0.RELEASE</org.springframework-version> <org.springframework.security-version>3.2.0.RELEASE</org.springframework.security-version> <org.aspectj-version>1.8.1</org.aspectj-version> <org.slf4j-version>1.6.1</org.slf4j-version> <org.mybatis>3.2.8</org.mybatis> <org.mybatis.spring>1.2.2</org.mybatis.spring> <org.aspectj>1.7.4</org.aspectj> <common.dbcp>1.4</common.dbcp> <mysql.version>5.1.34</mysql.version> </properties> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${org.springframework-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${org.springframework-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${org.springframework-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${org.springframework-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${org.springframework-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${org.springframework-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${org.springframework-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${org.springframework-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${org.springframework-version}</version> </dependency> <!--aspectJ--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.5</version> </dependency> <!-- Mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${org.mybatis}</version> </dependency> <!--Mybatis Spring--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>${org.mybatis.spring}</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <!-- 连接池 --> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>${common.dbcp}</version> </dependency> <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${org.slf4j-version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${org.slf4j-version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${org.slf4j-version}</version> <scope>runtime</scope> </dependency> <!-- Servlet --> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-servlet-api</artifactId> <version>7.0.30</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl-api</artifactId> <version>1.2</version> <exclusions> <exclusion> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> </exclusion> </exclusions> </dependency> <!-- Jackson JSON Processor --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.4.1</version> </dependency> <!-- File Upload --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.0.1</version> </dependency> </dependencies> </project> ~~~ ###           3.3 配置Mybatis配置文件         位于Maven的资源文件目录下: resources/mybatis/mybatis-config.xml ~~~ <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 命名空间 --> <typeAliases> <package name="org.star.coding.passion.web.model"/> </typeAliases> <plugins> <!--分页插件、只拦截映射文件中sql语句id以ByPage结尾的sql语句、仅支持mysql和Oracle分页--> <plugin interceptor="org.star.coding.passion.web.interceptor.PageInterceptor"> <property name="dialect" value="mysql"/> </plugin> </plugins> <!-- 映射map --> <mappers/> </configuration> ~~~ ###           3.4 配置Spring配置文件        主要是Spring与Mybatis集成、以及Spring事务以及连接池的配置、同时使用扫描加载除Controller注解标识的bean ~~~ <?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:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 引入资源文件--> <context:property-placeholder location="classpath:properties/jdbc-config.properties"/> <context:component-scan base-package="org.star.coding.passion"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- common dbcp 数据库连接池配置信息--> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" p:url="${jdbc.url}" p:driverClassName="${jdbc.driverClassName}" p:username="${jdbc.username}" p:password="${jdbc.password}" p:minEvictableIdleTimeMillis="${jdbc.minEvictableIdleTimeMillis}" p:removeAbandoned="${jdbc.removeAbandoned}" p:maxActive="${jdbc.maxActive}" p:maxIdle="${jdbc.maxIdle}" p:initialSize="${jdbc.initialSize}" p:minIdle="${jdbc.minIdle}" p:maxWait="${jdbc.maxWait}" p:validationQuery="${jdbc.validationQuery}" p:timeBetweenEvictionRunsMillis="${jdbc.timeBetweenEvictionRunsMillis}" p:testOnBorrow="${jdbc.testOnBorrow}" p:testOnReturn="${jdbc.testOnReturn}" p:testWhileIdle="${jdbc.testWhileIdle}" p:removeAbandonedTimeout="${jdbc.removeAbandonedTimeout}"/> <!-- spring与mybatis整合 读取所有mybatis配置文件--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:dataSource-ref="dataSource" p:configLocation="classpath:mybatis/mybatis-config.xml" p:mapperLocations="classpath:mybatis/mapper/*.xml"/> <!-- configLocation为mybatis属性 mapperLocations为所有mapper--> <!-- spring与mybatis整合配置,扫描所有mapper下mybatis配置文件、用动态代理模式来获取所有mybatis的xml文件中namespace指 向的接口的代理实例。--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:basePackage="org.star.coding.passion.web.mapper" p:sqlSessionFactoryBeanName="sqlSessionFactory"/> <!-- 对数据源进行事务管理--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"/> <!-- 事物切面配置--> <tx:advice id="advice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED" read-only="false"/> <tx:method name="save*" propagation="REQUIRED" read-only="false"/> <tx:method name="del*" propagation="REQUIRED" read-only="false"/> <tx:method name="remove*" propagation="REQUIRED" read-only="false"/> <tx:method name="get*" propagation="REQUIRED" read-only="true"/> <tx:method name="query*" propagation="REQUIRED" read-only="true"/> <tx:method name="sel*" propagation="REQUIRED" read-only="true"/> <tx:method name="mod*" propagation="REQUIRED" read-only="false"/> <tx:method name="update*" propagation="REQUIRED" read-only="false"/> </tx:attributes> </tx:advice> <tx:annotation-driven transaction-manager="transactionManager" /> <aop:config> <aop:pointcut id="servicePoint" expression="execution (* org.star.coding.passion.web.service..*.*(..))"/> <aop:advisor advice-ref="advice" pointcut-ref="servicePoint"/> </aop:config> </beans> ~~~ ###           3.5 配置SpringMVC配置文件        SpringMVC配置信息、仅扫描Controller注解标注的类 ~~~ <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"> <!-- 扫描controller类--> <context:component-scan base-package="org.star.coding.passion.web.controller"/> <!-- 允许静态资源访问--> <mvc:annotation-driven /> <mvc:default-servlet-handler/> <!-- 避免IE在ajax请求时,返回json出现下载--> <bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> </list> </property> </bean> <!-- 对模型视图添加前后缀 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/views/" p:suffix=".jsp"/> <!-- 上传文件配置 、多了个多请求的处理、目的是为了支持多文件上传--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" p:defaultEncoding="utf-8" p:maxUploadSize="10485760000" p:maxInMemorySize="40960"/> </beans> ~~~ ###           3.6 配置web.xml文件        配置常用信息 ~~~ <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <!-- web root --> <context-param> <param-name>webAppRootKey</param-name> <param-value>springmvc.root</param-value> </context-param> <!-- Spring configuration--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-root-context.xml</param-value> </context-param> <!-- log properties --> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>classpath:properties/log4j.properties</param-value> </context-param> <context-param> <param-name>log4jRefreshInterval</param-name> <param-value>6000</param-value> </context-param> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--Spring MVC DispatcherServlet--> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/springMVC-root-context.xml</param-value> </init-param> <load-on-startup>2</load-on-startup> <!--<async-supported>true</async-supported>--> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- Spring encoding filter --> <filter> <filter-name>SpringEncodingFilter</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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>SpringEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- error handle page --> <error-page> <!-- wrong path --> <error-code>404</error-code> <location>/WEB-INF/views/error/404.jsp</location> </error-page> <error-page> <!-- no privilege --> <error-code>405</error-code> <location>/WEB-INF/views/error/405.jsp</location> </error-page> <error-page> <!-- internal error --> <error-code>500</error-code> <location>/WEB-INF/views/error/500.jsp</location> </error-page> <error-page> <!--internal exception--> <exception-type>java.lang.Exception</exception-type> <location>/WEB-INF/views/error/400.jsp</location> </error-page> <!--welcome page list--> <welcome-file-list> <welcome-file>index.jsp</welcome-file> <welcome-file>welcome.jsp</welcome-file> </welcome-file-list> </web-app> ~~~ ###           3.7 配置Mybatis映射文件 ~~~ <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.star.coding.passion.web.mapper.UserMapper"> <select id="selectUserCount" resultType="int"> SELECT count(1) FROM klmy_person </select> </mapper> ~~~ ###           3.8 编写与Mybatis映射文件对应的映射接口 ~~~ package org.star.coding.passion.web.mapper; /** * Created by andy on 5/9/2015.<br> * Version 1.0-SNAPSHOT<br> */ public interface UserMapper { int selectUserCount(); } ~~~ ###           3.9 配置日志资源文件 ~~~ ### set log levels ### log4j.rootLogger = info , stdout , D , E ### output to the console ### log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [%c:%L] [%p] - %m%n ### output mybatis sql info to the console ### log4j.logger.org.star.coding.passion=DEBUG #log4j.logger.org.mybatis=DEBUG #log4j.logger.org.apache=DEBUG #log4j.logger.java.sql.Connection=DEBUG #log4j.logger.java.sql.Statement=DEBUG #log4j.logger.java.sql.PreparedStatement=DEBUG ### Output to the log file ### log4j.appender.D = org.apache.log4j.DailyRollingFileAppender log4j.appender.D.File = ${springmvc.root}/WEB-INF/logs/log.log log4j.appender.D.Append = true log4j.appender.D.Threshold = debug log4j.appender.D.layout = org.apache.log4j.PatternLayout log4j.appender.D.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [%t] [%c:%L] [%p] - %m%n ### Save exception information to separate file ### log4j.appender.E = org.apache.log4j.DailyRollingFileAppender log4j.appender.E.File = ${springmvc.root}/WEB-INF/logs/error.log log4j.appender.E.Append = true log4j.appender.E.Threshold = warn log4j.appender.E.layout = org.apache.log4j.PatternLayout log4j.appender.E.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [%t] [%c:%L] [%p] - %m%n ~~~ ### 四:补充        需要注意Spring的配置文件与SpringMVC的配置文件是分开的、两者分配在初始化Spring容器的时候扫描加载的bean不同、Spring是扫描加载除Controller注解以外符合加载条件的bean、而SpringMVC是只扫描加载Controller注解的bean、目的是为了在加载Controller时注入的Service是具有事务特性的bean。如果项目中事务不起作用、请查看是否是此原因导致。       更多内容:[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/45825599)        项目下载地址:http://download.csdn.net/detail/chenghuaying/8761971
';

Mybatis使用之分页

最后更新于:2022-04-01 16:30:02

# Mybatis使用之分页 ### 一:简介 **注:示例基于mysql数据库。Oracle可以自行测试。**    使用Mybatis分页主要有两种方式、一种是将分页参数传递到配置文件中、在写sql的时候就做分页。另一种是使用Mybatis的拦截器拦截需要分页的sql语句、重新组装成具有分页功能的sql语句。 分页查询的关键在于查询时需要告诉数据库从第几条开始取、取多少条记录。也就是常用到Page对象(一般是方便传递参数、自己构建的实体类)的pageNumer、pageSize两个主要参数。至于Page对象的总记录数等是需要带到前台构造数据展示表格时使用的参数。 ### 二:传递参数形式的分页 ### 2.1 关键点    传递参数形式的分页就是将分页信息以参数的形式传递到映射文件中、这样就可以在编写具体的sql语句时实现分页功能。这种方式的重点在于一般的查询语句都会带有一个或者多个查询参数、而'select'标签的parameterType只能接收一个值。所以需要我们将查询参数与分页参数组装成一个Map作为参数传递到映射语句中。 不过前台展示所需要的数据总数还需要另写一条count查询语句来获取。多多少少会有些不便。 ### 2.2 具体代码    映射文件: ~~~ <select id="selectAuthorWithPageParam"resultType="author" parameterType="hashMap"> SELECT t.id, t.username, t.password, t.email, t.bio, t.favourite_section favouriteSection FROM author t WHERE t.username = {username} AND t.password = {password} limit {page.dbIndex},{page.dbNumber} </select> ~~~    映射接口: ~~~ List<Author> selectAuthorWithPageParam(Map<String, Object>map); ~~~    测试代码: ~~~ @Test public void testSelectAuthorWithPageParam() throws Exception { Page page = new Page(); page.count(); Map<String, Object> map = new HashMap<>(); map.put("page", page); map.put("username", "alien"); map.put("password","alien"); List<Author> authors =this.authorMapper.selectAuthorWithPageParam(map); Assert.assertEquals(5, authors.size()); } ~~~ ### 三:拦截器分页 ### 3.1 关键点    拦截器实现分页的关键之处在于、在需要分页的sql语句执行之前、拦截下来并改造成具有分页功能的sql语句(还可以查询一下总数、设置到Page实体类中供前台展示数据时使用)、然后继续执行。 3.2 具体代码    说起来很简单、但是短短的一句话却需要理解许多东西才能达到目的。这里只挑一些重点步骤、具体拦截器工作原理、执行过程会在Mybatis深入过程详细分析。 从使用角度来考虑过程如下(***对所有映射语句id以‘ByPage'结尾的做分页处理***): #### 3.2.1 Page实体类: ~~~ package org.alien.mybatis.samples.entity; /** *Created by andy on 5/25/2015.<br> *Version 1.0-SNAPSHOT<br> */ @SuppressWarnings("unused") public class Page { /** * 总条数 */ private int totalNumber; /** * 当前第几页 */ private int currentPage; /** * 总页数 */ private int totalPage; /** * 每页显示条数 */ private int pageNumber = 5; /** * 数据库中limit的参数,从第几条开始取 */ private int dbIndex; /** * 数据库中limit的参数,一共取多少条 */ private int dbNumber; /** * Oracle 起始记录行号 */ private int rowNum; /** * Oracle 结束记录行号 */ private int rn; /** * 根据当前对象中属性值计算并设置相关属性值 */ public void count() { // 计算总页数 int totalPageTemp = this.totalNumber / this.pageNumber; int plus = (this.totalNumber % this.pageNumber) == 0 ? 0 : 1; totalPageTemp = totalPageTemp + plus; if(totalPageTemp <= 0) { totalPageTemp = 1; } this.totalPage = totalPageTemp; // 设置当前页数 // 总页数小于当前页数,应将当前页数设置为总页数 if(this.totalPage < this.currentPage) { this.currentPage = this.totalPage; } // 当前页数小于1设置为1 if(this.currentPage < 1) { this.currentPage = 1; } // 设置limit的参数 this.dbIndex = (this.currentPage - 1) * this.pageNumber; this.dbNumber = this.pageNumber; } public int getTotalNumber() { return totalNumber; } public void setTotalNumber(int totalNumber) { this.totalNumber = totalNumber; this.count(); } public int getCurrentPage() { return currentPage; } public void setCurrentPage(int currentPage) { this.currentPage = currentPage; } public int getTotalPage() { return totalPage; } public void setTotalPage(int totalPage) { this.totalPage = totalPage; } public int getPageNumber() { return pageNumber; } public void setPageNumber(int pageNumber) { this.pageNumber = pageNumber; this.count(); } public int getDbIndex() { return dbIndex; } public void setDbIndex(int dbIndex) { this.dbIndex = dbIndex; } public int getDbNumber() { return dbNumber; } public void setDbNumber(int dbNumber) { this.dbNumber = dbNumber; } public int getRn() { return (this.getCurrentPage() + 1) * this.getPageNumber(); } public void setRn(int rn) { this.rn = rn; } public int getRowNum() { return this.getCurrentPage() * this.getPageNumber(); } public void setRowNum(int rowNum) { this.rowNum = rowNum; } ~~~ #### 3.2.2 拦截器类: ~~~ packageorg.alien.mybatis.samples.interceptor; importorg.alien.mybatis.samples.entity.Page; importorg.apache.ibatis.executor.parameter.ParameterHandler; importorg.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; importorg.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; importorg.apache.ibatis.reflection.MetaObject; importorg.apache.ibatis.reflection.SystemMetaObject; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.Map; import java.util.Properties; /** *Page interceptor. *Support oracle and mysql. *Created by andy on 5/25/2015.<br> *Version 1.0-SNAPSHOT<br> */ @Intercepts({@Signature(type =StatementHandler.class, method = "prepare", args ={Connection.class})}) public class PageInterceptor implementsInterceptor { private String dialect; @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler =(StatementHandler) invocation.getTarget(); MetaObject metaObject = MetaObject.forObject(statementHandler,SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY); MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement"); String sqlId = mappedStatement.getId(); //intercept select sql witch end with "ByPage". if (sqlId.matches(".+ByPage$")) { BoundSql boundSql = (BoundSql)metaObject.getValue("delegate.boundSql"); String sql = boundSql.getSql(); ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler"); Map<?, ?> paramMap = (Map<?, ?>)parameterHandler.getParameterObject(); Page page = (Page) paramMap.get("page"); //set count Connection connection = (Connection) invocation.getArgs()[0]; String countSql = "select count(1) from (" + sql + ")a"; PreparedStatement preparedStatement =connection.prepareStatement(countSql); parameterHandler.setParameters(preparedStatement); ResultSet resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { page.setTotalNumber(resultSet.getInt(1)); } //construct record limit sql by dialect String pageSql; if ("oracle".equals(dialect.toLowerCase())) { pageSql = "select * from" + "(select a.*,rownum rn from (" + sql + ") a where rownum >= " +page.getRowNum() + ") " + "rn < " +page.getRn(); } else if ("mysql".equals(dialect.toLowerCase())) { pageSql = sql + " limit" + page.getDbIndex() + "," + page.getDbNumber(); } else { pageSql = sql; } metaObject.setValue("delegate.boundSql.sql", pageSql); } return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { this.dialect = properties.getProperty("dialect"); } } ~~~ #### 3.2.3 mybatis.xml总配置文件中注册拦截器 **注意Mybatis配置文件各个元素的顺序!** ~~~ <plugins> <plugin interceptor="org.alien.mybatis.samples.interceptor.PageInterceptor"> <!--database dialect, only support mysql and oracle--> <property name="dialect" value="mysql"/> </plugin> </plugins> ~~~ 3.2.4 映射文件语句 ~~~ <select id="selectAuthorByPage" resultType="author"parameterType="hashMap"> SELECT t.id, t.username, t.password, t.email, t.bio, t.favourite_section favouriteSection FROM author t WHERE t.username = {username} AND t.password = {password} </select> ~~~ #### 3.2.4 映射接口方法 ~~~ List<Author> selectAuthorByPage(Map<String, Object> map); ~~~ #### 3.2.4 测试方法 ~~~ @Test public void testSelectAuthorByPage() throws Exception { Page page = new Page(); Map<String, Object> map = new HashMap<>(); map.put("page", page); List<Author> authors = this.authorMapper.selectAuthorByPage(map); Assert.assertEquals(5, authors.size()); } ~~~ ### 三:补充       更多内容:[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/45825599)    篇幅有限、仅仅说明了拦截器的实现过程、原理及代码都没有详细说明、这些会在Mybatis深入中详细解析。
';

Mybatis深入之初始化过程

最后更新于:2022-04-01 16:30:00

# Mybatis深入之初始化过程 ### 一:简介 这篇开始是根据Mybatis源码来对Mybatis进行更深入的学习、当然、精力有限、还做不到学习的面面俱到。 Mybatis初始化过程可以用一句话概括:就是将Mybatis的配置信息加载到一个类中、供后面Mybatis进行各种操作时使用、这个类叫:Configuration——见名知意。当然这个类的功能并不仅限与存放配置文件信息。 ### 二:整体流程 下面是一段正常情况下从加载配置到执行sql语句的代码: ~~~ String mybatisConfigPath = "config/mybatis/mybatis.xml"; InputStream inputStream = Resources.getResourceAsStream(mybatisConfigPath); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); int count = (Integer)sqlSession.selectOne("org.alien.mybatis.samples.mapper.AuthorMapper.getAllAuthorsCount"); System.out.println(count); ~~~ 初始化过程在上面代码中就是获取SqlSessionFactory的过程。 初始化过程流程图: ![初始化过程流程图](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a8580362004.jpg "") 参照流程图、初始化大致步骤: 1. 加载配置文件 1. 解析配置文件、将配置文件中的信息装载到Configuration中。 1. 根据Configuration创建SqlSessionFactory并返回。 ### 三:详细过程 ### 3.1 加载配置文件 这一步很简单、从代码层面上来看就是将配置文件以流的形式读取到程序中、并将其作为参数传递给SqlSessionFactoryBuilder以供后面创建SqlSessionFactory。其提供了许多重载的方法供我们选择: ![创建方法](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a8580375607.jpg "") 但是其最后都是调用核心方法(从这里也可以看出、初始化过程就是构造填充Configuration过程): ~~~ public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } ~~~ ### 3.2解析配置文件 解析配置文件的入口是在SqlSessionFactoryBuilder中的:`public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)` 流程图: ![配置文件解析流程图](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a8580388268.jpg "") #### 3.2.1 整理流程: 1. 创建MybatisDTD文件实体类:XMLMapperEntityResolver. 1. 根据配置文件流信息和上一步创建的EntityResolver创建配置文件解析类:XPathParser用于解析配置文件内容. 1. 将前两部创建的对象作为XMLConfigBuilder的构造函数参数传递、创建XMLConfigBuiler对象. 1. 调用XMLConfigBuilder.parse()创建Configuration对象并将配置文件信息装配到Configuration对象中. 下面从代码的角度来看上面流程主要代码。这里从代码执行角度进行分析。 #### 3.2.2 代码流程 1. 从`SqlSessionFactoryBuilder.build()开始`: ~~~ public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { //创建解析文件并装配Configuration的类 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); //这里分开写、清楚一点。解析配置文件、装配Configuration并返回 Configuration configuration = parser.parse(); //根据Configuration创建SqlSessionFactory并返回 return build(configuration); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } ~~~ 先看`XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);`到底创建了一个什么样的XMLConfigBuilder。 具体构造方法: ~~~ public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); } ~~~ - 这里只关心inputstream参数、其他的可以自行研究、其实是不同build方法传递的不同参数。 - 从上面可以看出要想先构建XMLConfigBuilder、首先需要创建XMLMapperEntityResolver、并以其作为创建XPathParser对象的参数之一。 2、XMLMapperEntityResolver的创建:`new XMLMapperEntityResolver()`、即只需调用其无参构造函数即可。其源码就不再贴了、就是将Mybatis的DTD文件加载到一个私有集合中`private static final Map<String, String> doctypeMap = new HashMap<String, String>();`并向外提供一个用户获取DTD的InputSource的方法`public InputSource resolveEntity(String publicId, String systemId);` 3、XPathParser的创建: ~~~ public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) { //填充XPathParser 部分私有属性 commonConstructor(validation, variables, entityResolver); //根据InputStream来创建Document对象用于后面操作配置文件。 this.document = createDocument(new InputSource(inputStream)); } ~~~ - EntityResolver就是前面的XMLMapperEntityResolver - InputStream则是配置文件流信息 ~~~ private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) { this.validation = validation; this.entityResolver = entityResolver; this.variables = variables; XPathFactory factory = XPathFactory.newInstance(); this.xpath = factory.newXPath(); } ~~~ - 设置解析xml文件时使用的属性 ~~~ private Document createDocument(InputSource inputSource) { // important: this must only be called AFTER common constructor try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(validation); factory.setNamespaceAware(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(false); factory.setCoalescing(false); factory.setExpandEntityReferences(true); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver(entityResolver); builder.setErrorHandler(new ErrorHandler() { public void error(SAXParseException exception) throws SAXException { throw exception; } public void fatalError(SAXParseException exception) throws SAXException { throw exception; } public void warning(SAXParseException exception) throws SAXException { } }); return builder.parse(inputSource); } catch (Exception e) { throw new BuilderException("Error creating document instance. Cause: " + e, e); } } ~~~ - 根据InputSource创建Document 3、当XPathParser创建完成之后、回到真正执行XMLConfigBuilder创建的方法: ~~~ private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); //解析文件代码只能执行一次、当解析之后此值将变为true this.parsed = false; this.environment = environment; //前面实例化好的XPathParser this.parser = parser; } ~~~ - 调用Configuration无参构造方法创建其实例对象, - 设置XMLConfigBuilder解析装配Configuration需要用到的属性、其中最关键的`this.parser = parser`也就是前面实例化好的XPathParser。 - 有兴趣的可以看一眼Configuration实例化时初始化了哪些东西基本Mybatis的默认配置在这里都能找到 4、当XMLConfigBuilder实例化好之后、接下来就是解析配置文件、装配Configuration。 ~~~ public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; //对于parser.evalNode(String node)如何执行的、这里不关注。只需要知道parser.evalNode(String node)是干嘛的就行。 parseConfiguration(parser.evalNode("/configuration")); return configuration; } ~~~ - 获取配置文件中configuration节点所有信息包括其子节点。 ~~~ private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); //issue #117 read properties first typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); settingsElement(root.evalNode("settings")); environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } ~~~ - 从这里很明显的就看出此方法是将Mybatis配置文件各个配置项解析并装配到Configuration对象中。 5、这里只看其中一个最简单的过程——将Mybatis配置文件中的`<settings>...<setting name="xxx" value="xxx"/>...<settings>`解析并设置到Configuration中、其他的等后面涉及到会深入其过程之中 ~~~ private void settingsElement(XNode context) throws Exception { if (context != null) { Properties props = context.getChildrenAsProperties(); // Check that all settings are known to the configuration class MetaClass metaConfig = MetaClass.forClass(Configuration.class); for (Object key : props.keySet()) { if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); } } configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL"))); configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory"))); configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false)); configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true)); configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true)); configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true)); configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false)); configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE"))); configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null)); configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false)); configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION"))); configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER"))); configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString")); configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true)); configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage"))); configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); configuration.setLogPrefix(props.getProperty("logPrefix")); configuration.setLogImpl(resolveClass(props.getProperty("logImpl"))); configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); } } ~~~ 6、解析装配完成之后、返回Configuration ~~~ public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } ~~~ - 返回最终生成的DefaultSqlSessionFactory 到这里整个解析过程也就结束了。 ### 补充: 多提一句、网上有的说SqlSessionFactory的创建用到了创建者模式、觉得并不是那么恰当、建造者模式的核心是有一个调度员来根据不同的场景来调度不同的创建者创建具体对象。而这里并没有。个人觉得只是方法的一系列的重载、来方便使用者根据不同的场景或者喜好来创建SqlSessionFactory。 更多内容:[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/45825599 "Mybatis 目录")
';

Mybatis使用之日志相关

最后更新于:2022-04-01 16:29:58

# Mybatis使用之日志相关 ### 一:简介 Mybatis提供的日志功能可以帮助我们很只管的看到最后被执行的SQL语句、执行结果等信息。大大方便开发过程中的调试。下面主要摘自Mybatis官网。 ### 日志介绍 Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种方式: - SLF4J - Apache Commons Logging - Log4j 2 - Log4j - JDK logging 具体选择哪个日志实现由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。 如果一个都未找到,日志功能就会被禁用。 不少应用服务器的classpath中已经包含Commons Logging,如Tomcat和WebShpere, 所以MyBatis会把它作为具体的日志实现。记住这点非常重要。这意味着,在诸如 WebSphere的环境中——WebSphere提供了Commons Logging的私有实现,你的Log4J配置将被忽略。 这种做法不免让人悲摧,MyBatis怎么能忽略你的配置呢?事实上,因Commons Logging已经存 在,按优先级Log4J自然就被忽略了!不过,如果你的应用部署在一个包含Commons Logging的环境, 而你又想用其他的日志框架,你可以根据需要调用如下的某一方法: ~~~ org.apache.ibatis.logging.LogFactory.useSlf4jLogging(); org.apache.ibatis.logging.LogFactory.useLog4JLogging(); org.apache.ibatis.logging.LogFactory.useJdkLogging(); org.apache.ibatis.logging.LogFactory.useCommonsLogging(); org.apache.ibatis.logging.LogFactory.useStdOutLogging(); ~~~ 如果的确需要调用以上的某个方法,请在调用所有其他MyBatis方法前调用它。另外,只有在相应日志实现存在 的前提下,调用对应的方法才是有意义的,否则MyBatis一概忽略。如你环境中并不存在Log4J,你却调用了 相应的方法,MyBatis就会忽略这一调用,代之默认的查找顺序查找日志实现。 关于SLF4J、Apache Commons Logging、Apache Log4J和JDK Logging的API介绍已经超出本文档的范围。 不过,下面的例子可以作为一个快速入门。 ### 日志配置 具体怎么做,视使用的日志框架而定,这里以Log4J为例。配置日志功能非常简单:添加几个配置文件, 如log4j.properties,再增加个jar包,如log4j.jar。下面是具体的例子,共两个步骤: ### 步骤一:增加log4j jar包 因为采用Log4J,要确保在应用中对应的jar包是可用的。要满足这一点,只要将jar包添加到应用的classpath中即可。 Log4J的jar包可以从上方的链接下载。 具体而言,对于web或企业应用,需要将log4j.jar 添加到WEB-INF/lib 目录; 对于独立应用, 可以将它添加到jvm的 -classpath启动参数中。 ### 步骤二:配置log4j 配置Log4J比较简单, 比如需要记录这个mapper接口的日志: ~~~ package org.mybatis.example; public interface BlogMapper { @Select("SELECT * FROM blog WHERE id = #{id}") Blog selectBlog(int id); } ~~~ 只要在应用的classpath中创建一个名称为log4j.properties的文件, 文件的具体内容如下: ~~~ # Global logging configuration log4j.rootLogger=ERROR, stdout # MyBatis logging configuration... log4j.logger.org.mybatis.example.BlogMapper=TRACE # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n ~~~ 添加以上配置后,Log4J就会把 org.mybatis.example.BlogMapper 的详细执行日志记录下来,对于应用中的其它类则仅仅记录错误信息。 也可以将日志从整个mapper接口级别调整到到语句级别,从而实现更细粒度 ~~~ log4j.logger.org.mybatis.example.BlogMapper.selectBlog=TRACE ~~~ 与此相对,可以对一组mapper接口记录日志,只要对mapper接口所在的包开 ~~~ log4j.logger.org.mybatis.example=TRACE ~~~ 某些查询可能会返回大量的数据,只想记录其执行的SQL语句该怎么办?为此,Mybatis中SQL语 句的日志级别被设为DEBUG(JDK Logging中为FINE),结果日志的级别为TRACE(JDK Logging中为FINER)。所以,只要将日志级别调整为DEBUG即可达到目的: ~~~ log4j.logger.org.mybatis.example=DEBUG ~~~ 要记录日志的是类似下面的mapper文件而不是mapper接口又该怎么呢? ~~~ <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.mybatis.example.BlogMapper"> <select id="selectBlog" resultType="Blog"> select * from Blog where id = #{id} </select> </mapper> ~~~ 对这个文件记录日志,只要对命名空间增加日志记录功能即可: ~~~ log4j.logger.org.mybatis.example.BlogMapper=TRACE ~~~ 进一步,要记录具体语句的日志可以这样做: ~~~ log4j.logger.org.mybatis.example.BlogMapper.selectBlog=TRACE ~~~ ### 补充 更多内容:[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/45825599)
';

Mybatis使用之动态SQL语句

最后更新于:2022-04-01 16:29:55

# Mybatis使用之动态SQL语句 ### 一:简介        Mybatis动态SQL语句可帮助我们根据需要动态拼接SQL语句。主要在配置文件中使用<where> <if><choose><when><otherwise> <set> <trim><foreach>标签来实现。 ### 二:具体使用方式 ###        2.1 where              2.1.1 功能              语句的作用主要是简化SQL语句中where中的条件判断,where元素的作用是会在写入where元素的地方输出一个where,另外一个好处是你不需要考虑where元素里面的条件输出是什么样子 的,MyBatis会智能的帮你处理,如果所有的条件都不满足那么MyBatis就会查出所有的记录,如果输出后是and开头的,MyBatis会把第一个and忽略,当然如果是or开头的,MyBatis也会把它忽略;此外,在where元素中你不需要考虑空格的问 题,MyBatis会智能的帮你加上。              2.1.2 映射文件配置: ~~~ <select id="getBlogByConditionDynamic" resultMap="oneToManyNested"> select t1.id blog_id, t1.title, t2.id post_id, t2.section from blog t1 left outer join post t2 on t1.id = t2.blog_id <where> <if test="title != null and title !=''"> and t1.title like '%'||#{title}||'%' </if> <if test="section != null and section != ''"> and t2.section like '%'||#{section}||'%' </if> </where> </select> ~~~              2.1.3 映射接口方法: ~~~ List<Blog> getBlogByConditionDynamic(@Param("title") String title, @Param("section") String section); ~~~              2.1.4 测试方法: ~~~ @Test public void testGetBlogByConditionDynamic() throws Exception { List<Blog> blogList = blogMapper.getBlogByConditionDynamic("", "section"); Assert.assertNotNull(blogList); } ~~~ ###        3.1 if              3.1.1 功能              简单的条件判断,利用if语句我们可以实现某些简单的条件选择。判断为则拼接、否则忽略。              3.1.2 映射文件配置: ~~~ <select id="getBlogDynamic" resultMap="oneToManyNested"> select t1.id blog_id, t1.title, t2.id post_id, t2.section from blog t1 left outer join post t2 on t1.id = t2.blog_id where 1=1 <if test="title != null and title !=''"> and t1.title like '%'||#{title}||'%' </if> <if test="section != null and section != ''"> and t2.section like '%'||#{section}||'%' </if> </select> ~~~              3.1.3 映射接口方法: ~~~ List<Blog> getBlogDynamic(@Param("title") String title, @Param("section") String section); ~~~              3.1.4 测试方法: ~~~ public class BlogMapperTest { private BlogMapper blogMapper; public BlogMapperTest() { blogMapper = MybatisUtil.getSqlSession().getMapper(BlogMapper.class); } @Test public void testGetBlogDynamic() throws Exception { List<Blog> blogList = blogMapper.getBlogDynamic("title", "section"); Assert.assertNotNull(blogList); } ~~~                            ###        4.1 choose        4.1.1 功能              **choose**元素的作用就相当于JAVA中的switch语句,基本上跟JSTL中的choose的作用和用法是一样的,通常都是与when和otherwise搭配的。when元素表示当when中的条件满足的时候就输出其中的内容,跟JAVA中的switch效果差不多的是按照条件的顺序,当when中有条件满足的时 候,就会跳出choose,即所有的when和otherwise条件中,只有一个会输出,当所有的我很条件都不满足的时候就输出otherwise中的 内容。              4.1.2 映射文件配置: ~~~ <select id="getBlogByTitleOrSection" resultMap="oneToManyNested"> select t1.id blog_id, t1.title, t2.id post_id, t2.section from blog t1 left outer join post t2 on t1.id = t2.blog_id where 1=1 <choose> <when test="title != null and title !=''"> and t1.title like '%'||#{title}||'%' </when> <when test="section != null and section != ''"> and t2.section like '%'||#{section}||'%' </when> <otherwise> and t1.title is not null and t2.section is not null </otherwise> </choose> </select> ~~~              4.1.3 映射接口方法: ~~~ List<Blog> getBlogByTitleOrSection(@Param("title") String title, @Param("section") String section); ~~~              4.1.4 测试方法: ~~~ @Test public void testGetBlogByTitleOrSection() throws Exception { List<Blog> blogList = blogMapper.getBlogByTitleOrSection("", ""); Assert.assertNotNull(blogList); } ~~~ ###               5.1 set                     5.1.1 功能                     set元素主要是用在更新操作的时候,它的主要功能和where元素其实是差不多的,主要是在包含的语句前输出一个set,然后如果包含的语句是以逗号结束的话将会把该逗号忽略,如果set包含的内容为空的话则会出错。有了set元素我们就可以动态的更新那些修改了的字段。                     5.1.2 映射文件配置: ~~~ <update id="updateBlogTitleSet" parameterType="blog"> update blog <set> <if test="title != null and title != ''"> title = #{title} </if> <if test="id != null and id != ''"> , id = #{id} </if> </set> where id = #{id} </update> ~~~                     5.1.3 映射接口方法: ~~~ int updateBlogTitleSet(Blog blog); ~~~                     5.1.4 测试方法: ~~~ @Test public void testUpdateBlogTitleSet() throws Exception { Blog blog = new Blog(1, ""); int flag = this.blogMapper.updateBlogTitleSet(blog); Assert.assertEquals(true, flag > 0); } ~~~ ###        6.1 trim                     6.1.1 功能                     trim元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是 prefix和suffix;可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是prefixOverrides和 suffixOverrides;正因为trim有这样的功能,所以我们也可以非常简单的利用trim来代替where元素的功能                     6.1.2 映射文件配置: ~~~ <select id="getBlogByConditionDynamicTrim" resultMap="oneToManyNested"> select t1.id blog_id, t1.title, t2.id post_id, t2.section from blog t1 left outer join post t2 on t1.id = t2.blog_id <trim prefix="where" prefixOverrides="and | or"> <if test="title != null and title !=''"> and t1.title like '%'||#{title}||'%' </if> <if test="section != null and section != ''"> and t2.section like '%'||#{section}||'%' </if> </trim> </select> ~~~                     6.1.3 映射接口方法: ~~~ List<Blog> getBlogByConditionDynamicTrim(@Param("title") String title, @Param("section") String section); ~~~                     6.1.4 测试方法: ~~~ @Test public void testGetBlogByConditionDynamicTrim() throws Exception { List<Blog> blogList = blogMapper.getBlogByConditionDynamicTrim("title", "section"); Assert.assertNotNull(blogList); } ~~~ ###        7.1 foreach              7.1.1 功能 **foreach**的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach元素的属性主要有item,index,collection,open,separator,close。item表示集合中每一个元素进行迭代时的别名,index指 定一个名字,用于表示在迭代过程中,每次迭代到的位置,open表示该语句以什么开始,separator表示在每次进行迭代之间以什么符号作为分隔 符,close表示以什么结束,在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况 下,该属性的值是不一样的,主要有一下3种情况: 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,**当然单参数也可以封装成map**,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key 以Array为例:              7.1.2 映射文件配置: ~~~ <select id="dynamicForeach2Test" resultType="Blog"> select * from t_blog where id in <foreach collection="array" index="index" item="item" open="(" separator="," close=")"> #{item} </foreach> </select> ~~~              7.1.3 映射接口方法: ~~~ public List<Blog> dynamicForeach2Test(int[] ids); ~~~              7.1.4 测试方法: ~~~ @Test public void dynamicForeach2Test() { SqlSession session = MybatisUtil.getSqlSessionFactory().openSession(); BlogMapper blogMapper = session.getMapper(BlogMapper.class); int[] ids = new int[] {1,3,6,9}; List<Blog> blogs = blogMapper.dynamicForeach2Test(ids); } ~~~ ### 三:补充       更多内容:[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/45825599)        github地址:https://github.com/andyChenHuaYing/scattered-items/tree/master/items-mybatis        源码下载地址:http://download.csdn.net/detail/chenghuaying/8713311
';

Mybatis使用之参数传递

最后更新于:2022-04-01 16:29:53

# Mybatis使用之参数传递 ### 一:简介             主要记录Mybatis是如何映射传递的参数的。分四种来记录:1、java基本类型的传递、2、Java对象形式传递 3、多参数传递4、集合类型参数传递 ### 二:具体方式 ###       2.1 java基本类型       以整形为例、映射文件: ~~~ <select id="getAuthorById" parameterType="int" resultType="org.alien.mybatis.samples.model.Author"> SELECT * FROM author WHERE id = #{id} </select> ~~~       映射方法: ~~~ Author getAuthorById(int id); ~~~       测试代码: ~~~ private AuthorMapper authorMapper; public AuthorMapperTest() { authorMapper = MybatisUtil.getSqlSession().getMapper(AuthorMapper.class); } @Test public void testGetAuthorById() throws Exception { Author author = authorMapper.getAuthorById(1); Assert.assertNotNull(author); } ~~~ ###       2.2. java对象             以Author类为例、映射文件: ~~~ <select id="getAuthorWithValidate" parameterType="org.alien.mybatis.samples.model.Author" resultType="org.alien.mybatis.samples.model.Author"> SELECT * FROM author WHERE username = #{username} AND password = #{password} </select> ~~~       映射方法: ~~~ Author getAuthorWithValidate(Author author); ~~~       测试方法: ~~~ @Test public void testGetAuthorWithValidate() throws Exception { Author author = authorMapper.getAuthorWithValidate(new Author("star_year", "alien")); Assert.assertNotNull(author); } ~~~ ###       2.3 多参数       多参数分为两种:1、将参数放在Map中。 2、使用Mybatis的注解@Param来指定。       Map类型映射文件: ~~~ <select id="getAuthorByMultiCondition" parameterType="hashMap" resultType="author"> SELECT * FROM ( SELECT A.*, ROWNUM RN FROM (SELECT * FROM author WHERE username LIKE '%' || #{username} || '%') A WHERE ROWNUM <= #{endRecord} ) WHERE RN >= #{startRecord} </select> ~~~       Map类型映射方法: ~~~ List<Author> getAuthorByMultiCondition(Map<String, Object> map); ~~~       Map类型测试方法: ~~~ @Test public void testGetAuthorByMultiCondition() throws Exception { Map<String, Object> map = new HashMap<>(); map.put("startRecord", 0); map.put("endRecord", 10); map.put("username", "star_year"); List<Author> authors = authorMapper.getAuthorByMultiCondition(map); Assert.assertNotNull(authors); } ~~~       @Param类型映射文件: ~~~ <select id="getAuthorByUsername" resultType="Author"> SELECT * FROM author WHERE username LIKE '%' || #{username} || '%' AND email LIKE '%' || #{email} || '%' </select> ~~~       @Param类型映射方法: ~~~ List<Author> getAuthorByUsername(@Param("username") String username, @Param("email") String email); ~~~       @Param类型测试方法: ~~~ @Test public void testGetAuthorByUsername() throws Exception { List<Author> authorList = authorMapper.getAuthorByUsername("star_year", "46185"); Assert.assertNotNull(authorList); } ~~~ ###       2.4 集合类型参数       常用与根据一系列id来做一些操作。       以Author类为例、映射文件: ~~~ <select id="getAuthorByIdCollection" resultType="author"> select * from author where id in <foreach collection="list" item="item" open="(" separator="," close=")"> #{item} </foreach> </select> ~~~       映射方法: ~~~ List<Author> getAuthorByIdCollection(List<Integer> idList); ~~~       测试方法: ~~~ @Test public void testGetAuthorByIdCollection() throws Exception { List<Integer> idList = new ArrayList<>(); idList.add(1); idList.add(2); List<Author> authorList = authorMapper.getAuthorByIdCollection(idList); Assert.assertNotNull(authorList); } ~~~ ### 三:${paramName} 与 #{paramName}区别        使用#{parameterName}引用参数的时候,Mybatis会把这个参数认为是一个字符串,例如传入参数是“Smith”,那么在SQL(Select * from emp where name = #{employeeName})使用的时候就会转换为Select * from emp where name = 'Smith';同时在SQL(Select * from emp where name = ${employeeName})使用的时候就会转换为Select * from emp where name = Smith。       再次,从安全性上考虑,能使用#尽量使用#来传参,因为这样可以有效防止SQL注入的问题。 ### 四:补充       更多内容:[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/45825599)        github地址:https://github.com/andyChenHuaYing/scattered-items/tree/master/items-mybatis        源码下载地址:http://download.csdn.net/detail/chenghuaying/8713311       Author 类: ~~~ public class Author { private int id; private String username; private String password; private String email; private String bio; private String favouriteSection; //getter setter ... } ~~~
';

Mybatis使用之查询详解

最后更新于:2022-04-01 16:29:51

# Mybatis使用之查询详解 ### 一:简介        此篇主要知识点:       1、sql语句块       2、select标签属性       3、ResultMap 标签配置       4、一对一关联       5、一对多关联       6、多对多关联       7、嵌套查询 ### 二:SQL语句块 ###        2.1 作用              SQL元素可以被用来定义可重用的SQL 代码段,可以包含在其他语句中。比如在项目中常常做的分页查询、分别需要两条SQL语句、一条是查询所有具体记录信息、另一条需要查询记录数、而两条语句的where条件是一样的。这种类似情况就可以使用SQL语句块来简化配置信息。 ###        2.2 实例              2.2.1 SQL语句定义:       ~~~ <sql id="queryColumns"> id, username, password </sql> ~~~              2.2.2 使用: ~~~ <select id="queryAuthor" parameterType="int" resultType="author"> SELECT <include refid="queryColumns"/> FROM author WHERE id = #{id} </select> ~~~ ### 三:select标签属性 ###        3.1 标签属性集合: ~~~ <select id="selectPerson" parameterType="int" parameterMap="deprecated" resultType="hashmap" resultMap="personResultMap" flushCache="false" useCache="true" timeout="10000" fetchSize="256" statementType="PREPARED" resultSetType="FORWARD_ONLY"> ~~~ ###        3.2具体意义:              参见补充部分。 ### 四:ResultMap 标签配置 ###        4.1 介绍              resultMap 元素是MyBatis 中最重要最强大的元素。它就是让你远离90%的需要从结果集中取出数据的 JDBC 代码的那个东西, 而且在一些情形下允许你做一些 JDBC 不支持的事情。事实上, 编写相似于对复杂语句联合映射这些等同的代码, 也许可以跨过上千行的代码。 ResultMap 的设计就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。 ###        4.2 ResultMap 与ResultType的区别              MyBatis中在查询进行select映射的时候,返回类型可以用resultType,也可以用resultMap。resultType是直接表示返回类型的,而resultMap则是对外部ResultMap的引用但是resultType跟resultMap不能同时存在。 在MyBatis进行查询映射的时候,其实查询出来的每一个属性都是放在一个对应的Map里面的,其中键是属性名,值则是其对应的值。当提供的返回类型属性是resultType的时候,MyBatis会将Map里面的键值对取出赋给resultType所指定的对象对应的属性。 所以其实MyBatis的每一个查询映射的返回类型都是ResultMap,只是当我们提供的返回类型属性是resultType的时候,MyBatis对自动的给我们把对应的值赋给resultType所指定对象的属性,而当我们提供的返回类型是resultMap的时候,Mybatis就会根据我们配置的信息做映射 ###        4.3 标签属性 ~~~ <resultMap id="resultMapId" type="JavaBean" autoMapping="false" extends=""/> ~~~ ###        4.4 标签体              ResultMap的主要组成部分、并且标签体之间可以相互嵌套、来表示更复杂的JavaBean。具体的使用到对应的标签体再说明其作用与标签属性的配置及意义。              下面是其标签体签名: ~~~ <constructor> <idArg/> <arg/> </constructor> <id property="" column="" javaType="" jdbcType="" typeHandler=""/> <result typeHandler="" jdbcType="" javaType="" column="" property=""/> <association property=""/> <collection property=""/> <discriminator javaType=""> <case value=""/> </discriminator> ~~~ ### 五:简单对象select映射 ###         5.1 ResultType类型返回值的表示方式:                         因为在前面提到过、加载Mybatis总配置文件的时候会自动扫描指定包下面的类、并且用类名首字母小写作为名称作为其别名(TypeAliases)、这样我们就不必在select的resultType指定JavaBean的全限定名称了。select语句如下即可: ~~~ <select id="getAllAuthors" resultType="author"> SELECT t.id, t.username, t.password, t.email, t.bio, t.favourite_section favouriteSection FROM author t </select> ~~~ ###         5.2 ResultMap类型返回值的表示方式:                           5.2.1 配置ResultMap ~~~ <resultMap id="author" type="author"> <id property="id" column="author_id"/> <result property="username" column="username"/> <result property="password" column="password"/> <result property="email" column="email"/> <result property="bio" column="bio"/> <result property="favouriteSection" column="favouriteSection"/> </resultMap> ~~~              5.2.2 将select标签的ResultMap属性值设置为上面ResultMap标签的id、达到映射效果 ~~~ <select id="getAllAuthors" resultMap="author"> SELECT t.id, t.username, t.password, t.email, t.bio, t.favourite_section favouriteSection FROM author t </select> ~~~              5.2.3 当前ResultMap中 id result子表签作用及属性意义             ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a857c703fb1.jpg) ### 六:一对一关联       ###       6.1 一对一场景       以Blog为例、从[《Mybatis使用之环境搭建》](http://blog.csdn.net/crave_shy/article/details/45825631)中知道Blog与Author是一对一关系、这里是在Blog中关联了Author信息。其他的先不管。如何查询Blog信息、要求是除基本信息外还包含作者完整信息       6.1.1 ResultMap配置一: ~~~ <resultMap id="oneToOneBlog" type="blog"> <id property="id" column="blog_id"/> <result property="title" column="title"/> <!-- 可以使用resultMap属性指向其他映射文件已经定义好的resultMap --> <association property="author" resultMap="org.alien.mybatis.samples.mapper.AuthorMapper.author"/> </resultMap> ~~~       6.1.2 ResultMap配置二: ~~~ <resultMap id="oneToOneBlog" type="blog"> <id property="id" column="blog_id"/> <result property="title" column="title"/> <!-- 也可以在此内部定义指向Author类的映射、此种定义不能重用 --> <association property="author" javaType="author"> <id property="id" column="author_id"/> <result property="username" column="username"/> <result property="password" column="password"/> <result property="email" column="email"/> <result property="bio" column="bio"/> <result property="favouriteSection" column="favouriteSection"/> </association> </resultMap> ~~~       6.1.3查询语句: ~~~ <select id="getBlogOneToOne" resultMap="oneToOneBlog"> SELECT t1.id blog_id, t1.title, t2.id author_id, t2.username, t2.password, t2.email, t2.bio, t2.favourite_section favouriteSection FROM blog t1, author t2 WHERE t1.author_id = t2.id </select> ~~~       6.1.4 association标签属性以及标签体            属性:      ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a857c716bf4.jpg) ### 七:一对多关联       ###       7.1 一对多场景       以Blog为例、从[《Mybatis使用之环境搭建》](http://blog.csdn.net/crave_shy/article/details/45825631)中知道Blog与Post是一对多关系、这里是在Blog中有一个集合存放类型为Post的List。       7.1.1 ResultMap配置一: ~~~ <resultMap id="oneToMany" type="blog"> <id property="id" column="blog_id" javaType="int"/> <result property="title" column="title" javaType="string"/> <collection property="posts" ofType="post"> <id property="id" column="post_id"/> <result property="section" column="section"/> </collection> </resultMap> ~~~       7.1.2 ResultMap配置二: ~~~ <resultMap id="oneToManyResultMap" type="blog"> <id property="id" column="blog_id" javaType="int"/> <result property="title" column="title" javaType="string"/> <collection property="posts" ofType="post" resultMap="org.alien.mybatis.samples.mapper.PostMapper.post"/> </resultMap> ~~~       7.1.3查询语句: ~~~ <!--<select id="getBlogWithPosts" resultMap="oneToMany">--> <select id="getBlogWithPosts" resultMap="oneToManyResultMap"> SELECT t1.id blog_id, t1.title, t2.id post_id, t2.section FROM blog t1 LEFT OUTER JOIN post t2 ON t1.id = t2.blog_id WHERE t1.id = 1 </select> ~~~            7.1.4 collection标签属性:       “ofType”属性。这个属性用来区分JavaBean(或字段)属性类型和集合包含的类型(类型明确下也可以省略)。比如: ~~~ <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/> 读作: “在 Post 类型的 ArrayList 中的 posts 的集合。” javaType 属性是不需要的,因为 MyBatis 在很多情况下会为你算出来。所以你可以缩短 写法: <collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/> ~~~ ### 八:多对多关联        多对多关联可以简化为单方面一对多关联。可以参见一对多关联的方式实现。 ### 九:嵌套关联 ###       9.1 一对多场景       以Blog为例、从[《Mybatis使用之环境搭建》](http://blog.csdn.net/crave_shy/article/details/45825631)中知道Blog与Post是一对多关系、Post与PostComment(文章评论)Tag(文章标签)是一对多、当在查询Blog属性时、关联的Post属性一并查出来、同时PostComment和Tag属性关联出来。       9.1.1 ResultMap配置: ~~~ <resultMap id="oneToManyNested" type="blog"> <id property="id" column="blog_id" javaType="int"/> <result property="title" column="title" javaType="string"/> <collection property="posts" ofType="post"> <id property="id" column="post_id"/> <result property="section" column="section"/> <collection property="comments" resultMap="org.alien.mybatis.samples.mapper.PostCommentMapper.postComment"/> <collection property="tags" ofType="tag"> <id property="id" column="tag_id"/> <result property="name" column="tag_name"/> </collection> </collection> </resultMap> ~~~       9.1.2 查询语句: ~~~ <select id="getBlogWithPostsNested" resultMap="oneToManyNested"> SELECT t1.id blog_id, t1.title, t2.id post_id, t2.section, t3.id post_comment_id, t3.name post_comment_name, t3.comment_text post_comment_text, t5.id tag_id, t5.name tag_name FROM blog t1 LEFT OUTER JOIN post t2 ON t1.id = t2.blog_id LEFT OUTER JOIN post_comment t3 ON t2.id = t3.post_id LEFT OUTER JOIN post_tag t4 ON t2.id = t4.post_id LEFT OUTER JOIN tag t5 ON t4.tag_id = t5.id WHERE t1.id = 1 </select> ~~~ ### 十:缓存       MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。MyBatis 3中的缓存实现的很多改进都已经实现了,使得它更加强大而且易于配置。 默认情况下是没有开启缓存的,除了局部的 session 缓存,可以增强变现而且处理循环依赖也是必须的。要开启二级缓存,你需要在你的 SQL 映射文件中添加一行: ~~~ <cache/> ~~~ 字面上看就是这样。这个简单语句的效果如下: - 映射语句文件中的所有 select 语句将会被缓存。 - 映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。 - 缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。 - 根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。 - 缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。 - 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。 所有的这些属性都可以通过缓存元素的属性来修改。比如: ~~~ <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> ~~~ 这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。 可用的收回策略有: - LRU – 最近最少使用的:移除最长时间不被使用的对象。 - FIFO – 先进先出:按对象进入缓存的顺序来移除它们。 - SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。 - WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。 默认的是 LRU。 flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。 size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是 1024。 readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。 这里暂时没有自定义缓存、后面会有专门关于缓存的记录。 ### 补充:       更多内容:[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/45825599)        github地址:https://github.com/andyChenHuaYing/scattered-items/tree/master/items-mybatis        源码下载地址:http://download.csdn.net/detail/chenghuaying/8713311       select标签属性对应意义: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a8580326a80.jpg) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a858033b60e.jpg)
';

Mybatis使用之增删改详解

最后更新于:2022-04-01 16:29:49

# Mybatis使用之增删改详解 ### 一:简介        主要记录Mybatis的增删改使用到的标签属性及意义。参数方面没有过多涉及、后面还是会有单独章节来记录参数的类型及使用。        这里没有查询、因为查询算是比较重要的使用方式、同时也比较复杂、牵涉到ResultMap的配置、关联查询等。分开的目的是想让博客每一篇都有重点、调理清晰一点。 ### 二:新增 ###        2.1 insert标签属性及意义:       ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a857c6ca7a2.jpg) ###        2.2 insert之返回生成主键:              2.2.1对于MySql或者SQL Server这种主键自增长的数据库可以使用如下方式获取主键(其中两个关键标签属性useGeneratedKeys 和keyProperty的意义见上面insert标签属性): ~~~ <insert id="addAuthorWithAutoGeneratedKey" useGeneratedKeys="true" keyProperty="id"> INSERT INTO author (username, password, email, bio, favourite_section) VALUES (#{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR}) </insert> ~~~              2.2.2对于Oracle这种不支持主键自增长的数据库可以使用下面这种先生成一个主键再插入表并将生成主键返回的方式来笨处理: ~~~ <insert id="addAuthorWithoutAutoGeneratedKey"> <selectKey keyProperty="id" resultType="int" order="BEFORE"> select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1 </selectKey> insert into Author (id, username, password, email,bio, favourite_section) values (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR}) </insert> ~~~              2.2.3对于Oracle一般主键都会使用Sequence来实现、此时返回新增记录的主键的方式如下:              ~~~ <insert id="addAuthor" parameterType="author"> INSERT INTO author(id, username) VALUES (seq_author.nextval, #{username}) <selectKey keyProperty="id" resultType="int"> SELECT max(id) FROM author </selectKey> </insert> ~~~              2.2.4 selectKey: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a857c6e384e.jpg) ###        2.3 实例:              2.3.1 Mysql中向Author表中插入一条数据并返回主键 ~~~ <insert id="addAuthorWithAutoGeneratedKey" useGeneratedKeys="true" keyProperty="id"> INSERT INTO author (username, password, email, bio, favourite_section) VALUES (#{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR}) </insert> ~~~              2.3.2Oracle中向Author表中插入一条数据并返回主键 ~~~ <insert id="addAuthor" parameterType="author"> INSERT INTO author(id, username) VALUES (seq_author.nextval, #{username}) <selectKey keyProperty="id" resultType="int"> SELECT max(id) FROM author </selectKey> </insert> ~~~ ### 三:修改、删除       将两个放在一起的原因是两者使用的方式比较简单、其中修改使用方式基本和新增差不多。 ###        3.1 修改              3.1.1update标签及标签属性 ~~~ <update id="" parameterType="author" statementType="PREPARED" flushCache="true" timeout="20" databaseId="" keyProperty="" keyColumn="" useGeneratedKeys="true"/> ~~~                           具体标签属性意义见2.2              3.1.2修改实例             当其未设定主键为返回值时、返回的是数据库中影响的行数。比如修改一条记录、则方法的返回结果为1. ~~~ <update id="updateAuthor" parameterType="author" flushCache="true" statementType="PREPARED"> UPDATE author SET username = #{username} WHERE id = #{id} </update> ~~~ ###        3.2 删除              3.2.1delete标签及标签属性              返回的是数据库中影响的行数。比如删除10条记录、则方法的返回结果为10. ~~~ <delete id="" parameterType="author" statementType="PREPARED" flushCache="true" timeout="20"/> ~~~              3.2.2实例 ~~~ <delete id="deleteAuthor" parameterType="int"> DELETE FROM author WHERE id = #{id} </delete> ~~~ ### 四:补充        更多内容:[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/45825599)        github地址:https://github.com/andyChenHuaYing/scattered-items/tree/master/items-mybatis        源码下载地址:http://download.csdn.net/detail/chenghuaying/8713311
';

Mybatis使用之简单的增删改查

最后更新于:2022-04-01 16:29:46

# Mybatis使用之简单的增删改查 ### 一:简介             主要记录最简单的数据的增删改查、下一章会有各个操作详细一点的配置说明。以Author表为例(见上一博客)、Author表没有关联任何其他表、也没有特殊字段。 ### 二:映射规则 ###        2.1、映射文件中的sql方法与对应的XxxMapper接口中的方法映射规则:              a)映射文件的namespace的值是XxxMapper接口的全限定名、即包名+接口名称              b)映射文件中表示增删改查的标签(select、insert、delete、update)的id的值是接口中方法名(id具有唯一性)              c)映射文件中增删改查标签的表示参数的属性指定的值对应接口中方法的参数类型              d)映射文件中增删改查标签的表示返回值的属性指定的值对应接口中方法的参数类型 ###        2.2、举例说明:              现在需要根据一个int型的id来查询一个作者的具体信息。              a)AuthorMapper中定义此方法 ~~~ <span style="color:#FF0000;">package org.alien.mybatis.samples.mapper;</span> public interface AuthorMapper { Author <span style="color:#FF0000;">getAuthorById</span>(int id); } ~~~              b)AuthorMapper.xml中关联此方法 ~~~ <pre name="code" class="html"><span style="color:#FF0000;"><mapper namespace="org.alien.mybatis.samples.mapper.AuthorMapper"></span> <select id="<span style="color:#FF0000;">getAuthorById</span>" parameterMap="int" resultType="author"> SELECT t.id, t.username, t.password, t.email, t.bio, t.favourite_section favouriteSection FROM author t WHERE t.id=#{id} </select> ~~~ ~~~ ~~~              c)执行方法(见下文) ### 三:增删改查实现      ###        3.1、流程图:                   3.1.1 开发流程图(顺序可不同) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a857c680693.jpg)              3.1.2 执行UML序列图 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a857c6b34be.jpg) ###        3.2、具体实现过程:                   3.2.1、mybatis配置文件配置              详细信息见上一篇、这里重点对一下两点进行说明              a)自动扫描指定包下面所有JavaBean、并将其类名首字母小写作为key来代表当前JavaBean、用于映射文件配置增删改查标签时入参或者返回值类型是此JavaBean类型的结果集的时候。比如Author。当根据id查询某一个具体的Author的信息的时候、select标签的resultType的值就可以直接使用author来代替Author全路径名。入参同样。具体配置片段: ~~~ <typeAliases> <!--That is domain.blog.Author will be registered as author. If the @Alias annotation is found its value will be used as an alias.@Alias("author")--> <package name="org.alien.mybatis.samples.model"/> </typeAliases> ~~~              b)自动 扫描指定包下面所有的映射文件、并加载到内存中。这样配置的好处就是不用为每一个映射文件都要配置一次。具体配置片段: ~~~ <mappers> <package name="org.alien.mybatis.samples.mapper"/> </mappers> ~~~              3.2.2、AuthorMapper.xml映射文件配置              a)添加              通过insert标签定义              b)查询              通过select标签定义              c)修改              通过update标签定义              d)删除              通过delete标签定义 ~~~ <mapper namespace="org.alien.mybatis.samples.mapper.AuthorMapper"> <select id="getAuthorById" parameterMap="int" resultType="author"> SELECT t.id, t.username, t.password, t.email, t.bio, t.favourite_section favouriteSection FROM author t WHERE t.id=#{id} </select> <insert id="addAuthor" parameterType="author"> INSERT INTO author(id, username) VALUES (#{id}, #{username}) <selectKey keyProperty="id" resultType="int"> SELECT max(id) FROM author </selectKey> </insert> <delete id="deleteAuthor" parameterType="int"> DELETE FROM author WHERE id = #{id} </delete> <update id="updateAuthor" parameterType="author"> UPDATE author SET username = #{username} </update> <select id="getAllAuthors" resultType="author"> SELECT t.id, t.username, t.password, t.email, t.bio, t.favourite_section favouriteSection FROM author t </select> <select id="getAllAuthorsCount" resultType="int"> SELECT count(1) FROM author </select> </mapper> ~~~              3.2.3、编写AuthorMapper接口以及方法 ~~~ package org.alien.mybatis.samples.mapper; import org.alien.mybatis.samples.model.Author; import java.util.List; public interface AuthorMapper { int addAuthor(Author author); int deleteAuthor(int id); int updateAuthor(Author author); List<Author> getAllAuthors(); int getAllAuthorsCount(); Author getAuthorById(int id); } ~~~              3.2.4 加载配置文件、调用AuthorMapper方法 ~~~ package org.alien.mybatis.samples.service.impl; import org.alien.mybatis.samples.mapper.AuthorMapper; import org.alien.mybatis.samples.model.Author; import org.alien.mybatis.samples.service.AuthorService; import org.alien.mybatis.samples.util.MybatisUtil; import org.apache.ibatis.session.SqlSession; import java.util.List; /** * Created by andychen on 2015/5/18.<br> * Version 1.0-SNAPSHOT<br> */ public class AuthorServiceImpl implements AuthorService { /** * Add author info. * * @param author author instance * @return The key of current record in database. */ @Override public int addAuthor(Author author) { SqlSession sqlSession = null; try { sqlSession = MybatisUtil.getSqlSession(); AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class); int authorId = authorMapper.addAuthor(author); sqlSession.commit(); return authorId; } finally { if (sqlSession != null) { sqlSession.close(); } } } /** * Delete author info by author's id. * * @param authorId author id * @return int The number of rows affected by the delete. */ @Override public int deleteAuthor(int authorId) { SqlSession sqlSession = null; try { sqlSession = MybatisUtil.getSqlSession(); AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class); int result = authorMapper.deleteAuthor(authorId); sqlSession.commit(); return result; } finally { if (sqlSession != null) { sqlSession.close(); } } } /** * update author info * * @param author Author instance * @return int The number of rows affected by the update. */ @Override public int updateAuthor(Author author) { SqlSession sqlSession = null; try { sqlSession = MybatisUtil.getSqlSession(); AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class); int result = authorMapper.updateAuthor(author); sqlSession.commit(); return result; } finally { if (sqlSession != null) { sqlSession.close(); } } } /** * Query all authors * * @return all authors */ @Override public List<Author> getAllAuthors() { SqlSession sqlSession = null; try { sqlSession = MybatisUtil.getSqlSession(); AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class); return authorMapper.getAllAuthors(); } finally { if (sqlSession != null) { sqlSession.close(); } } } /** * Query all authors count * * @return all authors count */ @Override public int getAllAuthorsCount() { SqlSession sqlSession = null; try { sqlSession = MybatisUtil.getSqlSession(); AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class); return authorMapper.getAllAuthorsCount(); } finally { if (sqlSession != null) { sqlSession.close(); } } } @Override public Author getAuthorById(int id) { SqlSession sqlSession = null; try { sqlSession = MybatisUtil.getSqlSession(); AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class); return authorMapper.getAuthorById(id); } finally { if (sqlSession != null) { sqlSession.close(); } } } } ~~~ ### 四:补充        更多内容:[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/45825599)        github地址:https://github.com/andyChenHuaYing/scattered-items/tree/master/items-mybatis        源码下载地址:http://download.csdn.net/detail/chenghuaying/8713311        MybatisUtil:见上一篇博客
';

Mybatis使用之环境搭建

最后更新于:2022-04-01 16:29:44

# Mybatis使用之环境搭建 ### 一:简介             集成环境:IntellijIDEA 14.0+Maven+Mybatis3.2.8+mysql。       主要为后续Mybatis使用搭建运行环境。 ### 二:前期准备       1、 主要是数据库对应测试表的创建、这些表是后面学习过程中使用的表。数据模型简要ER图如下:            ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a857c66aad5.jpg)       2、 建表语句以及初始化数据、见补充部分。      ### 三:具体步骤            1、步骤概览:            ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a857c680693.jpg)       2、具体过程:              i、 Maven引入依赖 ~~~ <properties> <junit.version>4.1</junit.version> <testng.version>6.8.8</testng.version> <sources.plugin.verion>2.1.1</sources.plugin.verion> <org.springframework>4.1.1.RELEASE</org.springframework> <servlet-api>3.1.0</servlet-api> <jsp-api>2.0</jsp-api> <javax.inject>1</javax.inject> <javax.el>2.2.4</javax.el> <org.hibernate>5.0.2.Final</org.hibernate> <org.aspectj>1.7.4</org.aspectj> <c3p0.version>0.9.1.2</c3p0.version> <mysql.version>5.1.34</mysql.version> <slf4j.version>1.7.10</slf4j.version> <spring.ldap.version>2.0.2.RELEASE</spring.ldap.version> <commons.pool>1.6</commons.pool> <mybatis.version>3.2.8</mybatis.version> </properties> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> ~~~                   ii、 配置数据库连接资源文件      ~~~ jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/scattered-items jdbc.username=root jdbc.password=root ~~~              编写数据库映射接口及方法、以AuthorMapper为例: ~~~ public interface AuthorMapper { int addAuthor(Author author); int deleteAuthor(int id); int updateAuthor(Author author); List<Author> getAllAuthors(); int getAllAuthorsCount(); } ~~~              编写与数据库表对应实体、以Author为例: ~~~ @SuppressWarnings("unused") public class Author { private int id; private String username; private String password; private String email; private String bio; private String favouriteSection; //省略getter setter... @Override public String toString() { return "Author{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", email='" + email + '\'' + ", bio='" + bio + '\'' + ", favouriteSection='" + favouriteSection + '\'' + '}'; } } ~~~              iii、 配置mybatis整体配置文件 ~~~ <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- properties --> <properties resource="properties/jdbc.properties"/> <!--settings <settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings>--> <!--A type alias is simply a shorter name for a Java type.--> <typeAliases> <!--<typeAlias type="org.alien.mybatis.samples.model.Employee" alias="Employee"/>--> <!--<typeAlias type="org.alien.mybatis.samples.model.Department" alias="Department"/>--> <!--That is domain.blog.Author will be registered as author. If the @Alias annotation is found its value will be used as an alias.@Alias("author")--> <package name="org.alien.mybatis.samples.model"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <!--<mapper resource="config/mappers/Department.xml"/>--> <!--<mapper resource="config/mappers/Employee.xml"/>--> <!--<mapper resource="org/alien/mybatis/samples/config/Department.xml"/>--> <!--<mapper class="org.alien.mybatis.samples.dao.DepartmentMapper"/>--> <!-- Register all interfaces in a package as mappers --> <package name="org.alien.mybatis.samples.mapper"/> </mappers> </configuration> ~~~               iv、 配置mybatis映射文件                这里注意映射文件所放置的位置:resources/org/alien/mybatis/samples/mapper/AuthorMapper.xml。目的是为了在项目被编译之后AuthorMapper.xml文件是与AuthorMapper在同一文件夹内。至于为什么必须这样、后面会有。 ~~~ <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.alien.mybatis.samples.mapper.AuthorMapper"> <resultMap id="author" type="author"> <id property="id" column="author_id"/> <result property="username" column="username"/> <result property="password" column="password"/> <result property="email" column="email"/> <result property="bio" column="bio"/> <result property="favouriteSection" column="favouriteSection"/> </resultMap> <insert id="addAuthor" parameterType="author"> INSERT INTO author(id, username) VALUES (#{id}, #{username}) <selectKey keyProperty="id" resultType="int"> SELECT max(id) FROM author </selectKey> </insert> <delete id="deleteAuthor" parameterType="int"> DELETE FROM author WHERE id = #{id} </delete> <update id="updateAuthor" parameterType="author"> UPDATE author SET username = #{username} </update> <select id="getAllAuthors" resultType="author"> SELECT t.id, t.username, t.password, t.email, t.bio, t.favourite_section favouriteSection FROM author t </select> <select id="getAllAuthorsCount" resultType="int"> SELECT count(1) FROM author </select> </mapper> ~~~              配置日志文件(主要用于打印sql语句) ~~~ ### set log levels ### log4j.rootLogger = , console ### output to the console ### log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%l]-[%p] %m%n log4j.logger.org.alien.mybatis.samples.mapper=debug ~~~              vi、 AuthorServiceImpl: ~~~ /** * Created by andychen on 2015/5/18.<br> * Version 1.0-SNAPSHOT<br> */ public class AuthorServiceImpl implements AuthorService { /** * Add author info. * * @param author author instance * @return The key of current record in database. */ @Override public int addAuthor(Author author) { SqlSession sqlSession = null; try { sqlSession = MybatisUtil.getSqlSession(); AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class); int authorId = authorMapper.addAuthor(author); sqlSession.commit(); return authorId; } finally { if (sqlSession != null) { sqlSession.close(); } } } /** * Delete author info by author's id. * * @param authorId author id * @return int The number of rows affected by the delete. */ @Override public int deleteAuthor(int authorId) { SqlSession sqlSession = null; try { sqlSession = MybatisUtil.getSqlSession(); AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class); int result = authorMapper.deleteAuthor(authorId); sqlSession.commit(); return result; } finally { if (sqlSession != null) { sqlSession.close(); } } } /** * update author info * * @param author Author instance * @return int The number of rows affected by the update. */ @Override public int updateAuthor(Author author) { SqlSession sqlSession = null; try { sqlSession = MybatisUtil.getSqlSession(); AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class); int result = authorMapper.updateAuthor(author); sqlSession.commit(); return result; } finally { if (sqlSession != null) { sqlSession.close(); } } } /** * Query all authors * * @return all authors */ @Override public List<Author> getAllAuthors() { SqlSession sqlSession = null; try { sqlSession = MybatisUtil.getSqlSession(); AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class); return authorMapper.getAllAuthors(); } finally { if (sqlSession != null) { sqlSession.close(); } } } /** * Query all authors count * * @return all authors count */ @Override public int getAllAuthorsCount() { SqlSession sqlSession = null; try { sqlSession = MybatisUtil.getSqlSession(); AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class); return authorMapper.getAllAuthorsCount(); } finally { if (sqlSession != null) { sqlSession.close(); } } } } ~~~              vii、 测试AuthorService接口: ~~~ public class AuthorServiceImplTest { private AuthorService authorService; @Before public void setUp() throws Exception { authorService = new AuthorServiceImpl(); } @Test public void testGetAllAuthors() throws Exception { Assert.assertEquals(true, authorService.getAllAuthors().size() > 0); } @Test public void getAllAuthorsCount() throws Exception { Assert.assertEquals(true, authorService.getAllAuthorsCount() > 0); } @Test public void testAddAuthor() throws Exception { Assert.assertEquals(true, authorService.addAuthor(new Author(3, "year")) > 0); } @Test public void testDeleteAuthor() throws Exception { Assert.assertEquals(true, authorService.deleteAuthor(3) > 0); } @Test public void testUpdateAuthor() throws Exception { Assert.assertEquals(true, authorService.updateAuthor(new Author(2, "star_year")) > 0); } } ~~~ ### 四:补充       更多内容:[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/45825599)        github地址:https://github.com/andyChenHuaYing/scattered-items/tree/master/items-mybatis        源码下载地址:http://download.csdn.net/detail/chenghuaying/8713311       MybatisUtil类: ~~~ /** * Loading mybatis config and mappers file. * Created by andychen on 2015/5/7.<br> * Version 1.0-SNAPSHOT<br> */ public class MybatisUtil { private static SqlSessionFactory sqlSessionFactory; /** * Singleton model to get SqlSessionFactory instance. * * @return SqlSessionFactory instance */ public static SqlSessionFactory getSqlSessionFactory() { String mybatisConfigPath = "config/mybatis/mybatis.xml"; try { InputStream inputStream = Resources.getResourceAsStream(mybatisConfigPath); if (sqlSessionFactory == null) { sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } } catch (IOException e) { e.printStackTrace(); } return sqlSessionFactory; } /** * Open a SqlSession via SqlSessionFactory. * By the way, you should close the SqlSession in your code. * * @return SqlSession sqlSession instance. */ public static SqlSession getSqlSession() { return MybatisUtil.getSqlSessionFactory().openSession(); } } ~~~       建表SQL语句: ~~~ /* Navicat MySQL Data Transfer Source Server : scattered-items Source Server Version : 50096 Source Host : localhost:3306 Source Database : scattered-items Target Server Type : MYSQL Target Server Version : 50096 File Encoding : 65001 Date: 2015-05-16 12:30:10 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `author` -- ---------------------------- DROP TABLE IF EXISTS `author`; CREATE TABLE `author` ( `ID` int(11) NOT NULL auto_increment, `USERNAME` varchar(200) default NULL, `PASSWORD` varchar(200) default NULL, `EMAIL` varchar(200) default NULL, `BIO` varchar(200) default NULL, `FAVOURITE_SECTION` varchar(200) default NULL, PRIMARY KEY (`ID`), UNIQUE KEY `AUTHOR_INDEX` (`ID`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of author -- ---------------------------- INSERT INTO author VALUES ('1', 'alien', 'alien', '461857202@qq.com', null, 'java io'); -- ---------------------------- -- Table structure for `blog` -- ---------------------------- DROP TABLE IF EXISTS `blog`; CREATE TABLE `blog` ( `ID` int(11) NOT NULL auto_increment, `TITLE` varchar(200) default NULL, `AUTHOR_ID` int(11) default NULL, PRIMARY KEY (`ID`), UNIQUE KEY `BLOG_INDEX` (`ID`), KEY `BLOG_AUTHOR_FG` (`AUTHOR_ID`), CONSTRAINT `BLOG_AUTHOR_FG` FOREIGN KEY (`AUTHOR_ID`) REFERENCES `author` (`ID`) ON DELETE SET NULL ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of blog -- ---------------------------- INSERT INTO blog VALUES ('1', 'Mybatis tutorial', '1'); -- ---------------------------- -- Table structure for `post` -- ---------------------------- DROP TABLE IF EXISTS `post`; CREATE TABLE `post` ( `ID` int(11) NOT NULL auto_increment, `BLOG_ID` int(11) default NULL, `AUTHOR_ID` int(11) default NULL, `CREATED_ON` date default NULL, `SECTION` varchar(200) default NULL, `SUBJECT` varchar(200) default NULL, `DRAFT` varchar(200) default NULL, `BODY` varchar(200) default NULL, PRIMARY KEY (`ID`), UNIQUE KEY `POST_INDEX` (`ID`), KEY `POST_BLOG_FG` (`BLOG_ID`), CONSTRAINT `POST_BLOG_FG` FOREIGN KEY (`BLOG_ID`) REFERENCES `blog` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of post -- ---------------------------- INSERT INTO post VALUES ('1', '1', '1', '2015-05-16', 'Mybatis introduction', 'Mybatis', 'Mybatis series draft', 'How to lean mybatis ?'); -- ---------------------------- -- Table structure for `post_comment` -- ---------------------------- DROP TABLE IF EXISTS `post_comment`; CREATE TABLE `post_comment` ( `ID` int(11) NOT NULL auto_increment, `POST_ID` int(11) default NULL, `NAME` varchar(200) default NULL, `COMMENT_TEXT` varchar(200) default NULL, PRIMARY KEY (`ID`), UNIQUE KEY `POST_COMMENT_INDEX` (`ID`), KEY `POST_COMMENT_POST_FG` (`POST_ID`), CONSTRAINT `POST_COMMENT_POST_FG` FOREIGN KEY (`POST_ID`) REFERENCES `post` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of post_comment -- ---------------------------- INSERT INTO post_comment VALUES ('1', '1', 'comment', 'Keep updating'); -- ---------------------------- -- Table structure for `post_tag` -- ---------------------------- DROP TABLE IF EXISTS `post_tag`; CREATE TABLE `post_tag` ( `ID` int(11) NOT NULL auto_increment, `POST_ID` int(11) NOT NULL, `TAG_ID` int(11) NOT NULL, PRIMARY KEY (`ID`), UNIQUE KEY `POST_TAG_INDEX` (`ID`), KEY `POST_TAG_INDEX2` (`POST_ID`), KEY `POST_TAG_INDEX3` (`TAG_ID`), CONSTRAINT `POST_TAG_TAG` FOREIGN KEY (`TAG_ID`) REFERENCES `tag` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `POST_TAG_POST` FOREIGN KEY (`POST_ID`) REFERENCES `post` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of post_tag -- ---------------------------- INSERT INTO post_tag VALUES ('1', '1', '1'); INSERT INTO post_tag VALUES ('2', '1', '2'); INSERT INTO post_tag VALUES ('3', '1', '5'); -- ---------------------------- -- Table structure for `tag` -- ---------------------------- DROP TABLE IF EXISTS `tag`; CREATE TABLE `tag` ( `ID` int(11) NOT NULL auto_increment, `NAME` varchar(200) default NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of tag -- ---------------------------- INSERT INTO tag VALUES ('1', 'Mybatis'); INSERT INTO tag VALUES ('2', 'Java'); INSERT INTO tag VALUES ('3', 'JavaScript'); INSERT INTO tag VALUES ('4', 'Web'); INSERT INTO tag VALUES ('5', 'ORM framework'); INSERT INTO tag VALUES ('6', null); ~~~       项目整体结构: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a857c6954de.jpg)
';

前言

最后更新于:2022-04-01 16:29:42

> 原文出处:[Mybatis从入门到精通](http://blog.csdn.net/column/details/andyalien-mybatis.html) 作者:[crave_shy](http://blog.csdn.net/crave_shy) **本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!** # Mybatis从入门到精通 > 主要介绍Mybatis从基础使用、到源码分析。知其然、知其所以然。
';