MyBytais中的参数类型的封装#
在使用注解版做开发时,我们会在每个mapper中标记好入参的类型
简单类型#
MyBattis的参数传递是支持简单类型的,比如下面这种
<delete id="deleteUserById" parameterType="java.lang.Integer"> delete from user where id = #{id} </delete>
传递pojo对象#
看下面的代码和配置, 在编写sql时,我们直接指定参数的类型的Pojo对象
@Update({"update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id = #{id}"}) void updateUser(User user);
还有这种配置
<update id="updateUser" parameterType="com.changwu.pojo.User"> update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id = #{id}" </update>
那么,myBatis如何解析我们传递的pojo对象呢? 答案是使用ojnl(Object graphic navigation Language) 对象图导航语言, 实际是底层是通过对象的方法来获取数据,但是在写法上却省略了getXXX
比如: 我们想获取username, 按理说这样写user.getUserName()
但是在ojnl表达式来说表达成 user.username
, 于是我们就可以在sql中直接写上pojo中字段的属性名,MyBatis会自动完成从对象中,取值解析
注意点就是说,得sql中属性的顺序和pojo中属性的生命顺序保持一致,否则存进去的就是乱序的数值
传递pojo包装后的对象#
开发中可能会有各种各样的查询条件,其中,很多时候用来查询的条件不是简单的数据类型,而是一类对象, 举个例子: 如下
根据另一个封装类去查询用户列表,其中的QueryVo
并不是持久化在数据库中的对象,而是某几个字段封装类,于是我们像下面这样传递值
@Select("select * from user where username = #{user.username}") List<User> findUserByQueryVo(QueryVo vo);
xml版本
<select id="findByQueryVo" paramterType="com.changwu.vo.QueryVo" resultType="com.changwo.pojo.User"> select * from user where username like #{user.username} </select>
注意点: 传递pojo的包装类是有限制的, 在下面取值时,强制程序员不能把名字写错
user == vo对象中的属性名user
username == vo对象中的属性user中的属性名username
MyBytais中的结果类型的封装#
基于XML的resultMap#
前面的实验中,我们的pojo字段名和数据表中列表保持百分百一致,所以我们在resultType
标签中使用com.changwu.pojo.User
接收返回的数据时才没出任何差错,但是一般在现实的开发中,同时使用数据库的列名的命名风格和java的驼峰命名法,然而,当我们的pojo的属性名个sql中的列表不一致时, Mybatis是不能完成两者的赋值的
- 解决方法1: 取别名
注解版本的,默认支持驼峰命名法,意思和忽略大小写擦不多,但是如果两者名字忽略大小写之后还不一样就真的得配置取别名了
@Select("select * from user") @Select("select id as userId from user")
<select id="findAll" resultType="com.changwu.pojo.User"> select id as userId from user; </select>
- 解决方法2: 使用配置
resultMap
如下:
- id为当前resultMap的唯一身份标识
- type表示查询的实体类是哪个实体类
- property为java中的实体类属性名
- column为数据库中列名
- 在
select
标签中去除掉原来的resultType
,取而代之的是resultMap
<resultMap id="userMap" type="com.changwu.pojo.User"> <id property="userId" column="id"></id> <result property="userName" column="username"></result> </resultMap> <select id="findAll" resultMap="userMap"> select id as userId from user; </select>
解决方法3: 开启驼峰命名配置
<?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> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <setting name="useGeneratedKeys" value="true"/> </settings> </configuration>
但是如果两个字段的差异已经不是驼峰命名法可以解决的了,就只能去配置别名了
基于注解实现resultMap
#
当实体类中的属性和表中的字段命名出现严重不一致时,我们使用通过注解解决此类问题
同样property
中是java对象中的属性, column
为表中的列名
通过@Results
中的id属性值,使其他方法可以通过@ResultMap
复用已经存在的映射关系
@Select("select * from user") @Results(id = "userMap",value = { @Result(id = true,property = "",column = ""), @Result(id = true,property = "",column = ""), @Result(id = true,property = "",column = ""), @Result(id = true,property = "",column = ""), }) List<User> findAll(); @Select("select * from user where id = #{id}") @ResultMap(value = {"userMap"}) User findById(Integer id);
MyBatis的数据连接池#
如何配置#
在如下Mybatis主配置文件中的<dataSource type="POOLED">
<?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"> <!--mybatis的主配置文件--> <configuration> <properties resource="jdbcConfig.properties"> </properties> <!--配置环境--> <environments default="mysql"> <!--配置mysql的环境--> <environment id="mysql"> <!--配置事务的类型--> <transactionManager type="JDBC"/> <!--配置数据源--> <!--dataSource存在三个, 其中的POOLED池化的连接池--> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <!--指定映射配置文件的位置,也就是针对每个Dao的配置文件的位置--> <!--下面指定的xml配置文件的路径,需要和src下IUserDao接口的目录保持一致--> <mappers> <mapper class="com.changwu.dao.IUserDao"/> </mappers> </configuration>
POOLED#
采用传统的javax.sql.DataSource规范中的连接池,这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这是一种使得并发 Web 应用快速响应请求的流行处理方式。
特点: 使用完了的连接会被回收,而不是被销毁
其他相应的属性
- poolMaximumActiveConnections : 任意时间正在使用的连接数量,默认为10
- poolMaximumIdleConnections : 任意事件可能存在的空闲连接数
- poolMaximumCheckoutTime : 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
- poolTimeToWait: 默认是20秒, 如果花费了20秒还没有获取到连接,就打印日志然后重新尝试获取
- poolMaximumLocalBadConnectionTolerance : 就是如果当前的线程从连接池中获取到了一个坏的连接,数据源会允许他重新获取一次,但是重新尝试的次数不能超过 poolMaximumIdleConnections 与 poolMaximumLocalBadConnectionTolerance 之和。 默认值:3 (新增于 3.4.5)
- poolPingQuery: 用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动失败时带有一个恰当的错误消息
- poolPingEnabled: 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。
- poolPingConnectionsNotUsedFor : 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。
它的实现类是PooledDataSource
, 看下它的继承体系图如下,它实现javax.sql的接口规范
我们看下它的获取连接的实现代码如下: 可以看到,从他里面获取新的连接,不是无脑new, 而是受到最大连接数,空闲连接数,当前活跃数,工作连接数等因素的限制
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.isEmpty()) { // 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); 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()) { try { oldestActiveConnection.getRealConnection().rollback(); } catch (SQLException e) { log.debug("Bad connection. Could not roll back"); } } conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp()); conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp()); 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) { // ping to server and check the connection is valid or not 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 + poolMaximumLocalBadConnectionTolerance)) { 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."); } } } } } return conn; }
UNPOOLED#
这个数据源的实现只是每次被请求时打开和关闭连接。虽然有点慢,但对于在数据库连接可用性方面没有太高要求的简单应用程序来说,是一个很好的选择。 不同的数据库在性能方面的表现也是不一样的,对于某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源具有以下属性。
它存在如下的配置信息
- driver – 这是 JDBC 驱动的 Java 类的完全限定名(并不是 JDBC 驱动中可能包含的数据源类)。
- url – 这是数据库的 JDBC URL 地址。
- username – 登录数据库的用户名。
- password – 登录数据库的密码。
- defaultTransactionIsolationLevel – 默认的连接事务隔离级别。
- defaultNetworkTimeout – The default network timeout value in milliseconds to wait for the database operation to complete. See the API documentation of java.sql.Connection#setNetworkTimeout() for details.
作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可,例如:
driver.encoding=UTF8
这将通过 DriverManager.getConnection(url,driverProperties) 方法传递值为 UTF8 的 encoding 属性给数据库驱动。
它的实现类是UnpooledDataSource
, 看下它的继承体系图如下,它实现javax.sql的接口规范
我们看下它对获取连接的实现代码如下: 每一次获取连接都使用jdk底层的加载驱动,创建新的连接给用户使用
private Connection doGetConnection(Properties properties) throws SQLException { initializeDriver(); Connection connection = DriverManager.getConnection(url, properties); configureConnection(connection); return connection; } private synchronized void initializeDriver() throws SQLException { if (!registeredDrivers.containsKey(driver)) { Class<?> driverType; try { if (driverClassLoader != null) { driverType = Class.forName(driver, true, driverClassLoader); } else { driverType = Resources.classForName(driver); } // DriverManager requires the driver to be loaded via the system ClassLoader. // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html Driver driverInstance = (Driver)driverType.newInstance(); DriverManager.registerDriver(new DriverProxy(driverInstance)); registeredDrivers.put(driver, driverInstance); } catch (Exception e) { throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e); } } }
JNDI#
作为了解吧,这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用.
MyBatis中的事务管理器#
MyBatis中存在两种事务管理器如下:
JDBC#
xml配置
<transactionManager type="JDBC"> <property name="closeConnection" value="false"/> </transactionManager>
这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
相关编码的实现: 它通过sqlSession对象的commit方法,和rollback方法实现事务的提交和回滚
设置自动提交,使用openSession()
重载的方法
// 1. 读取配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml"); // 2. 创建SqlSessionFactory工厂 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream); // 3. 创建sqlSession SqlSession sqlSession = factory.openSession(true);
MANAGED#
这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期,默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为
<transactionManager type="MANAGED"> <property name="closeConnection" value="false"/> </transactionManager>
如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器, 因为 Spring 模块会使用自带的管理器来覆盖前面的配置
动态SQL#
MyBatis的动态sql为我们提供了什么功能呢? 举一个相似的场景,就是用户提交了username password 两个字段的信息到后端, 后端进行下一步校验,然后后端的程序员可能就的通过自己拼接sql来完成这个功能 类似这样select * from user where username = + username + and password = +password
,一个两个没事, 这是一个痛苦的事, 例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号
动态sql解决了这个问题
虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。
if#
最常用的一种是,将对象中满足if条件的字段当成sql中where的条件
举个例子,当用户名不为空时,按照用户名查找
<select id="findUserByCondition" resultType="com.changwu.pojo.User" parameterType="com.changwu.pojo.User"> select * from user where 1=1 <if test="userName != null"> and username = #{userName} </if> </select>
choose (when, otherwise)#
choose 类似java中的switch case 语句,像下面这样, 命中了某一种情况后不再匹配其他的情况,都没有命中的话执行默认的代码块
Integer i =1; switch (i) { case 1: //do break; case 2: //do break; default: //do }
示例: 从user表中检索,当userName不为空时,仅仅使用userName当成条件去匹配, 如果userName为空,则检查第二个条件是否满足,如果第二个条件满足了,则用第二个条件当成结果拼接到sql中,所有条件都没有就拼接<otherwise>
标签中的语句
<select id="findUserByConditionLike" resultType="com.changwu.pojo.User" parameterType="com.changwu.vo.QueryVo"> select * from user <where> <choose> <when test="userName != null"> and username like #{userName} </when> <when test="user != null and user.sex != null"> and sex = #{user.sex} </when> <otherwise> and count = 1 </otherwise> </choose> </where> </select>
trim (where, set)#
神奇的 AND title like #{title} , 看下面的例子
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG WHERE <if test="state != null"> state = #{state} </if> <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if> </select>
如果所有的if条件全都不成立,那么最终拼接的sql是这样的
SELECT * FROM BLOG WHERE
如果第一个条件不成立,而第二个条件成立,拼接成的sql是这样的
SELECT * FROM BLOG WHERE AND title like #{title}
以上的两个结果都将导致java程序运行失败,Mybatis推出<where>
标签解决了这个问题,像下面这样
被标签包围的if条件, 只有至少有一个if成立了,才会在sql中拼接上where 关键字, 如果仅仅只有一个if成立了,这个if还带有 and or的关键字样, where会自动踢除他们
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG <where> <if test="state != null"> state = #{state} </if> <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if> </where> </select>
foreach#
forEach的功能非常强大!它允许程序员指定一个集合,然后通过foreach标签遍历这个集合从而完成in语句的拼接
注意点1: collection 代表将要遍历的集合,下面我给他取名ids, 这个名字不是乱取的,对应着我的"com.changwu.vo.QueryVo" 这个vo中的一个属性名
注意点2:#{id}里面的名和item保持一致
<select id="selectUserInIds" resultType="com.changwu.pojo.User" parameterType="com.changwu.vo.QueryVo"> select * from user where id in <foreach item="id" collection="ids" open="(" separator="," close=")"> #{id} </foreach> </select>