zl程序教程

您现在的位置是:首页 >  后端

当前栏目

MyBatis 开发手册 (二)

mybatis开发 手册
2023-09-27 14:25:57 时间
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 : 任意时间正在使用的连接数量,默认为10poolMaximumIdleConnections : 任意事件可能存在的空闲连接数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的接口规范


image


我们看下它的获取连接的实现代码如下: 可以看到 从他里面获取新的连接 不是无脑new 而是受到最大连接数,空闲连接数 当前活跃数,工作连接数等因素的限制


private PooledConnection popConnection(String username, String password) throws SQLException {

 boolean countedWait false;

 PooledConnection conn null;

 long t System.currentTimeMillis();

 int localBadConnectionCount 

 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的接口规范


image


我们看下它对获取连接的实现代码如下: 每一次获取连接都使用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 

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 

神了!阿里P8纯手写出了这份10W字的MyBatis技术原理实战开发手册 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录
MyBatis 开发手册 (一) 这一遍看Mybatis的原因是怀念一下去年的 10月24号我写自己第一个项目时使用全配置文件版本的MyBatis,那时我们三个人刚刚大二,说实话,当时还是觉得MyBatis挺难玩的,但是今年再看最新版的Mybatis3.5.0, 还是挺有感觉的 Mybatis的官网一级棒...
Mybatis是如何向Spring注册Mapper的? 有时候我们需要自行定义一些注解来标记某些特定功能的类并将它们注入Spring IoC容器。比较有代表性的就是Mybatis的Mapper接口。假如有一个新的需求让你也实现类似的功能你该如何下手呢?今天我们就从Mybatis的相关功能入手来学习其思路并为我所用。