官网:https://github.com/mybatis/
官方文档:http://mybatis.github.io/mybatis-3/zh/index.html
官方API:http://mybatis.github.io/mybatis-3/zh/xref/index.html
Apache Shiro 是一个框架,可用于身份验证和授权。http://shiro.apache.org/
http://www.ibm.com/developerworks/cn/web/wa-apacheshiro/
Redis实战http://blog.csdn.net/it_man/article/details/9730559
Redis客户端之Spring整合Jedis http://haiziwoainixx.iteye.com/blog/2087115
Redis实战之Redis + Jedis http://blog.csdn.net/it_man/article/details/9730605
参考:《2015传智播客_Springmvc+Mybatis由浅入深全套视频教程》http://www.itcast.cn/news/20150512/13543033211.shtml
传智播客itcast网盘:http://pan.baidu.com/share/home?uk=3560277524#category/type=0
http://www.cnblogs.com/xdp-gacl/tag/MyBatis%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93/
http://blog.csdn.net/rootsuper/article/details/8537498
Internet [‘?nt?net] n. 因特网,互联网
abatis [‘?b?t?s] n. 鹿砦;有刺铁丝网
iBatis 是一个基于Java的持久层框架
MyBatis
MyBatis和SpringMVC通过订单商品 案例驱动
第一天:基础知识(重点,内容量多)
对原生态JDBC程序(单独使用JDBC开发)问题总结
MyBatis框架原理 (掌握)
MyBatis入门程序
用户的增、删、改、查
MyBatis开发DAO两种方法:
原始DAO开发方法(程序需要编写DAO接口和DAO实现类)(掌握)
MyBatis的Mapper接口(相当于DAO接口)代理开发方法(掌握)
MyBatis配置文件SqlMapConfig.xml
MyBatis核心:
MyBatis输入映射(掌握)
MyBatis输出映射(掌握)
MyBatis的动态SQL(掌握)
第二天:高级知识
订单商品数据模型分析
高级结果集映射(一对一、一对多、多对多)
MyBatis延迟加载
MyBatis查询缓存(一级缓存、二级缓存)
mybaits和spring进行整合(掌握)
MyBatis逆向工程
开发环境
java环境:jdk1.8
eclipse:mars
mysql:5.6
参考1.5节的内容。
需求:使用JDBC查询MySql数据库中用户表的记录。
创建Java工程,加入jar包:数据库驱动包(mysql5.1)。程序代码:
JdbcTest.java |
package app.test; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; // Description:通过单独的jdbc程序,总结其中的问题 public class JdbcTest { public static void main(String[]args) { Connection connection =null; //数据库连接 PreparedStatement preparedStatement =null; //预编译的Statement,使用它可以提高数据库的性能 ResultSet resultSet =null; //结果集 try { Class.forName("com.mysql.jdbc.Driver");//加载数据库驱动 connection = DriverManager.getConnection(//通过驱动管理类获取数据库链接 "jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","root", "root"); String sql = "select * from user where username = ?"; //定义sql语句?表示占位符 preparedStatement = connection.prepareStatement(sql); //获取预处理statement // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 preparedStatement.setString(1,"王五"); resultSet = preparedStatement.executeQuery();// 向数据库发出sql执行查询,查询出结果集 // 遍历查询结果集 while (resultSet.next()) { System.out.println(resultSet.getString("id") +" " + resultSet.getString("username")); } } catch (Exceptione) { e.printStackTrace(); } finally {//释放资源 if (resultSet !=null) { try { resultSet.close(); } catch (SQLExceptione) { e.printStackTrace(); } } if (preparedStatement !=null) { try { preparedStatement.close(); } catch (SQLExceptione) { e.printStackTrace(); } } if (connection !=null) { try { connection.close(); } catch (SQLExceptione) { e.printStackTrace(); } } } } } |
上边使用JDBC的原始方法(未经封装)实现了查询数据库表记录的操作。
1、 加载数据库驱动
2、 创建并获取数据库连接Connection
3、 创建JDBC Statement对象
4、 设置SQL语句
5、 设置SQL语句中的参数(使用PreparedStatement)
6、 通过Statement执行SQL并获取结果ResultSet
7、 对SQL执行结果进行解析处理
8、 释放资源(ResultSet、Preparedstatement、Connection)
1、 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能。使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响数据库性能。
解决方案:使用数据库连接池管理数据库连接。
2、 SQL语句在代码中硬编码,造成代码不易维护,实际应用SQL变化的可能较大,SQL变动需要改变java代码。
解决方案:将SQL语句配置在XML配置文件中,即使SQL变化,不需要对Java代码进行重新编译。
3、 向PreparedStatement中设置参数,对占位符号位置和设置参数值存在硬编码,因为SQL语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
解决方案:将SQL语句及占位符号和参数全部配置在XML中。
4、 对结果集解析存在硬编码(查询列名),SQL变化导致解析代码变化,系统不易维护。
解决方案:将查询的结果集,自动映射成POJO对象。
MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装。MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。
MyBatis本是apache的一个开源项目iBatis。2010年这个项目由Apache Software Foundation 迁移到了Google Code,并且改名为MyBatis,实质上MyBatis对iBatis进行一些改进。再后来托管到github下(https://github.com/mybatis/mybatis-3/releases)。
MyBatis是一个优秀的持久层框架,它对JDBC的操作数据库的过程进行封装,使开发者只需要关注SQL本身,而不需要花费精力去处理例如注册驱动、创建Connection、创建Statement、手动设置参数、结果集检索等JDBC繁杂的过程代码。通过MyBatis提供的映射方式,自由灵活生成(半自动化,大部分需要程序员编写SQL)满足需要SQL语句。
MyBatis通过XML或注解的方式将要执行的各种Statement(Statement、PreparedStatemnt、CallableStatement)配置起来,并通过Java对象和Statement中的SQL进行映射生成最终执行的SQL语句,最后由MyBatis框架执行SQL并将结果映射成Java对象并返回。
MyBatis可以将向PreparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象(输出映射)。
1、 MyBatis配置:SqlMapConfig.xml,此文件作为MyBatis的全局配置文件,配置了MyBatis的运行环境等信息。mapper.xml文件即SQL映射文件,文件中配置了操作数据库的SQL语句。此文件需要在SqlMapConfig.xml中加载。
2、 通过MyBatis环境等配置信息构造SqlSessionFactory即会话工厂
3、 由会话工厂创建SqlSession即会话,操作数据库需要通过SqlSession进行。
4、 MyBatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
5、 Mapped Statement也是MyBatis一个底层封装对象,它包装了MyBatis配置信息及SQL映射信息等。mapper.xml文件中一个SQL对应一个Mapped Statement对象,SQL的id即是Mapped Statement的id。
6、 Mapped Statement对SQL执行输入参数进行定义,包括HashMap、基本类型、POJO,Executor通过Mapped Statement在执行SQL前将输入的Java对象映射至SQL中,输入参数映射就是JDBC编程中对preparedStatement设置参数。
7、 Mapped Statement对SQL执行输出结果进行定义,包括HashMap、基本类型、POJO,Executor通过Mapped Statement在执行SQL后将输出结果映射至Java对象中,输出结果映射过程相当于JDBC编程中对结果的解析处理过程。
MyBaits的代码由github.com管理,地址:https://github.com/mybatis/mybatis-3/releases,下载mybatis-3.3.0.zip。或在http://search.maven.org/上搜索“mybatis”,下载mybatis-3.3.0.jar。
解压mybatis-3.3.0.zip,得到下面的目录结构:
mybatis-3.3.0.jar----mybatis的核心包
lib----mybatis的依赖包
mybatis-3.3.0.pdf----mybatis使用手册
sql_table.sql:记录表结构。
sql_data.sql:记录测试数据。在实际企业开发中,最后提供一个初始化数据脚本。
sql_table.sql |
/*!40101 SET NAMES utf8 */; /*!40101 SET SQL_MODE=‘‘*/; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE=‘NO_AUTO_VALUE_ON_ZERO‘ */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
/*Table structure for table `items` (‘商品) */ CREATE TABLE `items` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL COMMENT ‘商品名称‘, `price` float(10,1) NOT NULL COMMENT ‘商品定价‘, `detail` text COMMENT ‘商品描述‘, `pic` varchar(64) DEFAULT NULL COMMENT ‘商品图片‘, `createtime` datetime NOT NULL COMMENT ‘生产日期‘, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*Table structure for table `orderdetail` (订单项) */ CREATE TABLE `orderdetail` ( `id` int(11) NOT NULL AUTO_INCREMENT, `orders_id` int(11) NOT NULL COMMENT ‘订单id‘, `items_id` int(11) NOT NULL COMMENT ‘商品id‘, `items_num` int(11) DEFAULT NULL COMMENT ‘商品购买数量‘, PRIMARY KEY (`id`), KEY `FK_orderdetail_1` (`orders_id`), KEY `FK_orderdetail_2` (`items_id`), CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`items_id`) REFERENCES `items` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
/*Table structure for table `orders` (订单) */ CREATE TABLE `orders` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT ‘下单用户id‘, `number` varchar(32) NOT NULL COMMENT ‘订单号‘, `createtime` datetime NOT NULL COMMENT ‘创建订单时间‘, `note` varchar(100) DEFAULT NULL COMMENT ‘备注‘, PRIMARY KEY (`id`), KEY `FK_orders_1` (`user_id`), CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
/*Table structure for table `user` (用户) */ CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL COMMENT ‘用户名称‘, `birthday` date DEFAULT NULL COMMENT ‘生日‘, `sex` char(1) DEFAULT NULL COMMENT ‘性别‘, `address` varchar(256) DEFAULT NULL COMMENT ‘地址‘, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8; |
sql_data.sql |
/*Data for the table `items` */ insert into `items`(`id`,`name`,`price`,`detail`,`pic`,`createtime`) values (1,‘台式机‘,3000.0,‘该电脑质量非常好!!!!‘,NULL,‘2015-02-03 13:22:53‘),(2,‘笔记本‘,6000.0,‘笔记本性能好,质量好!!!!!‘,NULL,‘2015-02-09 13:22:57‘),(3,‘背包‘,200.0,‘名牌背包,容量大质量好!!!!‘,NULL,‘2015-02-06 13:23:02‘);
/*Data for the table `orderdetail` */ insert into `orderdetail`(`id`,`orders_id`,`items_id`,`items_num`) values (1,3,1,1),(2,3,2,3),(3,4,3,4),(4,4,2,3);
/*Data for the table `orders` */ insert into `orders`(`id`,`user_id`,`number`,`createtime`,`note`) values (3,1,‘1000010‘,‘2015-02-04 13:22:35‘,NULL),(4,1,‘1000011‘,‘2015-02-03 13:22:41‘,NULL),(5,10,‘1000012‘,‘2015-02-12 16:13:23‘,NULL);
/*Data for the table `user` */ insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (1,‘王五‘,NULL,‘2‘,NULL),(10,‘张三‘,‘2014-07-10‘,‘1‘,‘北京市‘),(16,‘张小明‘,NULL,‘1‘,‘河南郑州‘),(22,‘陈小明‘,NULL,‘1‘,‘河南郑州‘),(24,‘张三丰‘,NULL,‘1‘,‘河南郑州‘),(25,‘陈小明‘,NULL,‘1‘,‘河南郑州‘),(26,‘王五‘,NULL,NULL,NULL);
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; |
将SQL脚本在MySQL数据库中执行,完成创建数据库和表的操作,如下:
到此,前期的开发环境准备工作全部完成。
实现以下功能:
① 根据用户id查询一个用户信息
② 根据用户名称模糊查询用户信息列表
③ 添加用户
④ 更新用户
⑤ 删除用户
创建测试项目,普通Java项目或者是JavaWeb项目均可。
创建“Source Folder”conf,用于存放所有的配置文件。
在conf下创建一个“Folder”文件夹sqlmap,用于存放映射文件。
创建“Source Folder”test,用于存放所有的测试类。
工程结构如下图所示:
加入MyBatis核心包、依赖包、数据驱动包。
MyBatis使用Log4J输出日志。在conf(classpath,类路径)下创建log4j.properties如下:
# Global logging configuration # 在开发环境下日志级别要设置为DEBUG,生产环境下设置为INFO或ERROR # log4j.rootLogger=ERROR, stdout log4j.rootLogger=DEBUG, 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 |
MyBatis默认使用log4j作为输出日志信息。
配置MyBatis的运行环境,数据源、事务等。添加MyBatis的配置文件SqlMapConfig.xml:在conf文件夹下创建一个SqlMapConfig.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> <!-- 和Spring整合后environments配置将废除 --> <environments default="development"><!--development表示开发环境,work表示工作环境 --> <environment id="development"> <!-- 使用JDBC事务管理,事务控制由MyBatis管理 --> <transactionManager type="JDBC" /> <!-- 配置数据库连接池信息,由MyBatis管理 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/mybatis" /> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments> <mappers> <mapper resource="sqlmap/User.xml"/> </mappers> </configuration> |
SqlMapConfig.xml是MyBatis核心配置文件,上边文件的配置内容为数据源、事务管理。
PO类作为MyBatis进行SQL映射使用,PO类通常与数据库表对应,User.java如下:
User.java片段 |
package app.domain; import java.util.Date; public class User {//users表所对应的实体类 // 实体类的属性和表的字段名称一一对应 private int id; private String username;// 用户姓名 private String sex;// 性别 private Date birthday;// 生日 private String address;// 地址 // getter, setter & toString } |
映射文件命名:User.xml(原始iBatis的命名法则);mapper代理开发映射文件名称叫XxxMapper.xml,比如:UserMapper.xml、ItemsMapper.xml。
在映射文件中配置SQL语句。
在conf(classpath)下的sqlmap目录下创建SQL映射文件Users.xml:
Users.xml |
<?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"> <!-- namespace命名空间,作用就是对SQL进行分类化管理,用于隔离SQL语句。 注意:使用mapper代理方法开发,namespace有特殊重要的作用 --> <mapper namespace="test"> <!-- 在映射文件中配置很多SQL语句--> <!-- 需求:通过id查询用户表的记录--> <!-- 通过 select执行数据库查询 id:标识 映射文件中的 SQL 将sql语句封装到mappedStatement对象中,所以将id称为statement的id parameterType:指定输入 参数的类型,这里指定int型 #{}表示一个占位符号 #{id}:其中的id表示接收输入的参数,参数名称就是id,如果输入参数是简单类型,#{}中的参数名可以任意,可以value或其它名称 resultType:指定sql输出结果 的所映射的java对象类型,select指定resultType表示将单条记录映射成的java对象。 --> <!-- 根据id获取用户信息--> <select id="findUserById" parameterType="int" resultType="app.domain.User"> SELECT * FROM USER WHERE id=#{value} </select> </mapper> |
namespace:命名空间,用于隔离sql语句,后面会讲另一层非常重要的作用。
parameterType:定义输入到sql中的映射类型,#{id}表示使用preparedstatement设置占位符号并将输入变量id传到sql。
resultType:定义结果映射类型。
MyBatis框架需要加载映射文件,将Users.xml添加在SqlMapConfig.xml,如下:
SqlMapConfig.xml片段 |
<mappers> <mapper resource="sqlmap/User.xml"/> </mappers> |
创建Junit测试用例:MyBatisFirstTest.java
MyBatisFirstTest.java |
package app.test; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import app.domain.User; public class MyBatisFirstTest { private SqlSessionFactorysqlSessionFactory; //会话工厂 @Before public void createSqlSessionFactory()throws IOException { String resource = "SqlMapConfig.xml";// MyBatis配置文件 // 得到配置文件流 InputStream inputStream = Resources.getResourceAsStream(resource); // 使用SqlSessionFactoryBuilder从MyBatis的XML配置文件中创建会话工厂SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } } |
编写测试方法findUserByIdTest(),JDK 7之前的try-catch-finally写法:
MyBatisFirstTest.java片段 |
// 根据id查询用户信息,得到一条记录结果。使用try-catch-finally语法 @Test public void findUserByIdTest() { SqlSession sqlSession = null; //数据库会话实例 try { sqlSession = sqlSessionFactory.openSession();// 通过工厂创建数据库会话实例sqlSession /* 通过SqlSession操作数据库,查询单个记录,根据用户id查询用户信息 第一个参数:映射文件中statement的id,等于=namespace+"."+statement的id 第二个参数:指定和映射文件中所匹配的parameterType类型的参数 sqlSession.selectOne结果 是与映射文件中所匹配的resultType类型的对象 selectOne查询出一条记录 */ User user = sqlSession.selectOne("test.findUserById", 1); System.out.println(user); //输出用户信息 } catch (Exceptione) { e.printStackTrace(); } finally { if (sqlSession !=null) { sqlSession.close();//释放资源 } } } |
编写测试方法findUserByIdTest(),JDK 7之后的使用try-with-resource语法的写法:
MyBatisFirstTest.java片段 |
// 根据id查询用户信息,得到一条记录结果。使用JDK 7之后的try-with-resources语法 @Test public void findUserByIdTest() { // 通过工厂创建数据库会话实例sqlSession try(SqlSession sqlSession = sqlSessionFactory.openSession()) { // 通过SqlSession操作数据库,查询单个记录,根据用户id查询用户信息 // 第一个参数:映射文件中statement的id,等于=namespace+"."+statement的id // 第二个参数:指定和映射文件中所匹配的parameterType类型的参数 // sqlSession.selectOne结果 是与映射文件中所匹配的resultType类型的对象 // selectOne查询出一条记录 User user = sqlSession.selectOne("test.findUserById", 1); // 输出用户信息 System.out.println(user); } } |
使用User.xml,添加根据用户名称模糊查询用户信息的sql语句。
Users.xml片段 |
<!-- 根据用户名称模糊查询用户信息,可能返回多条 resultType:指定就是单条记录所映射的java对象 类型 ${}:表示拼接sql串,将接收到参数的内容不加任何修饰拼接在SQL中。 使用${}拼接sql,引起sql注入 ${value}:接收输入 参数的内容,如果传入类型是简单类型,${}中只能使用value --> <select id="findUserByName" parameterType="java.lang.String" resultType="app.domain.User"> SELECT * FROM user WHERE username LIKE ‘%${value}%‘ </select> |
MyBatisFirstTest.java片段 |
// 根据用户名称模糊查询用户列表 @Test public void findUserByNameTest() { try(SqlSession sqlSession = sqlSessionFactory.openSession()) { // list中的user和映射文件中resultType所指定的类型一致 // 使用selectOne()返回多于1行数据时,会抛出以下异常:org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3 //List<User> list = sqlSession.selectOne("test.findUserByName", "小明"); List<User> list = sqlSession.selectList("test.findUserByName","小明"); // ${}表示一个拼接符号,会引用sql注入,所以不建议使用${}。 // 下面的语句中传人参数"小明%‘ OR username LIKE ‘%",注入SQL,实际返回了全部数据。 //List<User> list = sqlSession.selectList("test.findUserByName", "小明%‘ OR username LIKE ‘%"); System.out.println(list); } } |
需求:在插入数据后,不返回主键值。
(1)映射文件
在SqlMapConfig.xml中添加:
Users.xml片段 |
<!-- 添加用户 parameterType:指定输入参数类型是POJO(包括用户信息) #{}中指定POJO的属性名,接收到POJO对象的属性值,MyBatis通过OGNL获取对象的属性值 --> <insert id="insertUser" parameterType="app.domain.User"> insert into user(username,birthday,sex,address) value(#{username},#{birthday},#{sex},#{address}) </insert> |
(2)测试程序
MyBatisFirstTest.java片段 |
// 添加用户信息 @Test public void insertUserTest() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { // 插入用户对象 User user = new User(); user.setUsername("王小军"); user.setBirthday(new Date()); user.setSex("1"); user.setAddress("河南郑州"); System.out.println("插入前的user:" +user); sqlSession.insert("test.insertUser",user); System.out.println("插入后的user:" +user); // 提交事务 sqlSession.commit(); } } |
(3)执行结果
需求:在插入数据后,返回自增的主键值。
(1)映射文件
通过修改SQL映射文件,可以将MySQL自增主键返回:
在SqlMapConfig.xml中添加:
Users.xml片段 |
<!-- 添加用户 parameterType:指定输入参数类型是POJO(包括用户信息) #{}中指定POJO的属性名,接收到POJO对象的属性值,MyBatis通过OGNL获取对象的属性值 --> <insert id="insertUser" parameterType="app.domain.User"> <!-- 将插入数据的主键返回,返回到user对象中 SELECT LAST_INSERT_ID():得到刚insert进去记录的主键值,只适用与自增主键 keyProperty:将查询到主键值设置到parameterType指定的对象的哪个属性 order:SELECT LAST_INSERT_ID()执行顺序,相对于insert语句来说它的执行顺序 resultType:指定SELECT LAST_INSERT_ID()的结果类型 --> <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> SELECT LAST_INSERT_ID() </selectKey> insert into user(username,birthday,sex,address) value(#{username},#{birthday},#{sex},#{address}) </insert> |
添加selectKey实现将主键返回:
keyProperty:返回的主键存储在POJO中的哪个属性;
order:selectKey的执行顺序,是相对与insert语句来说,由于mysql的自增原理执行完insert语句之后才将主键生成,所以这里selectKey的执行顺序为after;
resultType:返回的主键是什么类型;
LAST_INSERT_ID():是mysql的函数,返回auto_increment自增列新记录id值。
(2)测试程序
同上面一个测试程序。
(3)执行结果
执行思路:先通过uuid()查询到主键,将主键输入到SQL语句中。执行uuid()语句顺序相对于insert语句之前执行。
(1)新建user2表结构
使用MySQL的uuid()函数生成主键,需要修改表中id字段类型为string,长度设置成36位。
SELECT LENGTH(UUID()); 语句的执行结果为36。
/*Table structure for table `user` (用户) */ CREATE TABLE `user2` ( `id` char(36) NOT NULL, `username` varchar(32) NOT NULL COMMENT ‘用户名称‘, `birthday` date DEFAULT NULL COMMENT ‘生日‘, `sex` char(1) DEFAULT NULL COMMENT ‘性别‘, `address` varchar(256) DEFAULT NULL COMMENT ‘地址‘, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
(2)新建PO实体类User2
User2.java片段 |
package app.domain; import java.util.Date; public class User2 {//users表所对应的实体类 // 实体类的属性和表的字段名称一一对应 private String id; private String username;// 用户姓名 private String sex;// 性别 private Date birthday;// 生日 private String address;// 地址 // getter, setter & toString } |
(3)映射文件
需要增加通过select uuid()得到uuid值。通过修改SQL映射文件,可以将MySQL主键返回:
在SqlMapConfig.xml中添加:
Users.xml片段 |
<!-- 添加用户2 parameterType:指定输入 参数类型是POJO(包括 用户信息) #{}中指定POJO的属性名,接收到POJO对象的属性值,mybatis通过OGNL获取对象的属性值 --> <insert id="insertUser2" parameterType="app.domain.User2"> <!-- 将插入数据的主键返回,返回到user对象中。 使用mysql的uuid()生成主键。 执行过程: 首先通过uuid()得到主键,将主键设置到user对象的id属性中; 其次在insert执行时,从user对象中取出id属性值。 --> <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String"> SELECT uuid() </selectKey> insert into user2(id,username,birthday,sex,address) value(#{id},#{username},#{birthday},#{sex},#{address}) </insert> |
注意这里使用的order是“BEFORE”
(2)测试程序
MyBatisFirstTest.java片段 |
// 添加用户信息2 @Test public void insertUserTest2() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { // 插入用户对象 User2 user = new User2(); user.setUsername("王小军"); user.setBirthday(new Date()); user.setSex("1"); user.setAddress("河南郑州"); System.out.println("插入前的user:" +user); sqlSession.insert("test.insertUser2",user); System.out.println("插入后的user:" +user); // 提交事务 sqlSession.commit(); } } |
(3)执行结果
首先自定义一个序列且用于生成主键,selectKey使用如下:
Users.xml片段 |
<insert id="insertUser" parameterType=" app.domain.User "> <selectKey resultType="java.lang.Integer" order="BEFORE" keyProperty="id"> SELECT 自定义序列.NEXTVAL FROM DUAL </selectKey> INSERT INTO user(id,username,birthday,sex,address) values(#{id},#{username},#{birthday},#{sex},#{address}) </insert> |
注意这里使用的order是“BEFORE”
Users.xml片段 |
<!-- 删除 用户 根据id删除用户,需要输入id值 --> <delete id=" deleteUserById" parameterType="java.lang.Integer"> delete from user where id=#{id} </delete> |
MyBatisFirstTest.java片段 |
// 根据id删除 用户信息 @Test public void deleteUserTest() { try(SqlSession sqlSession = sqlSessionFactory.openSession()) { // 传入id删除用户 sqlSession.delete("test.deleteUserById", 25); // 提交事务 sqlSession.commit(); } } |
如果欲删除的id对应的记录不存在,更新数为0,不会报错。结果如下:
如果欲删除的id对应的记录存在,更新数为1。结果如下:
Users.xml片段 |
<!-- 根据id更新用户 分析: 需要传入用户的id 需要传入用户的更新信息 parameterType指定user对象,包括id和更新信息,注意:id必须存在 #{id}:从输入user对象中获取id属性值 --> <update id="updateUser" parameterType="app.domain.User"> update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id} </update> |
MyBatisFirstTest.java片段 |
// 更新用户信息 @Test public void updateUserTest()throws IOException { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { // 新建用户信息 User user = new User(); // 必须设置id user.setId(26); user.setUsername("王大军"); user.setBirthday(new Date()); user.setSex("2"); user.setAddress("河南郑州"); sqlSession.update("test.updateUser",user); // 提交事务 sqlSession.commit(); } } |
parameterType:在映射文件中通过parameterType指定输入参数的类型。MyBatis通过OGNL从输入对象中获取参数值拼接在SQL中。
resultType:在映射文件中通过resultType指定输出结果的类型。MyBatis将SQL查询结果的一行记录数据映射为resultType指定类型的对象。
#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行Java类型和JDBC类型转换,#{}可以有效防止SQL注入。#{}接收输入参数,类型可以是简单类型,POJO属性值、HashMap属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。#{}接收POJO属性值,通过OGNL读取对象中的属性值,通过属性.属性.属性...的方式获取对象属性值。
${}表示一个拼接符号,表示拼接SQL串,通过${}可以将parameterType传入的内容拼接在SQL中且不进行JDBC类型转换,${}可以接收简单类型值、POJO属性值或HashMap属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。${}接收POJO属性值,通过OGNL读取对象中的属性值,通过属性.属性.属性...的方式获取对象属性值。
${}表示一个拼接符号,会引用sql注入,所以不建议使用${}。
selectOne()表示查询出一条记录进行映射。如果使用selectOne可以实现使用selectList也可以实现(list中只有一个对象)。如果使用selectOne查询多条记录则抛出异常:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3 at …
selectList()表示查询出一个列表(多条记录)进行映射。如果使用selectList()查询多条记录,不能使用selectOne()。
1、 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。
2、 SQL语句写在代码中造成代码不易维护,实际应用SQL变化的可能较大,SQL变动需要改变Java代码。
解决:将SQL语句配置在XxxMapper.xml文件中与Java代码分离。
3、 向sql语句传参数麻烦,因为SQL语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决:Mybatis自动将Java对象映射至SQL语句,通过statement中的parameterType定义输入参数的类型。
4、 对结果集解析麻烦,SQL变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成POJO对象解析比较方便。
解决:Mybatis自动将SQL执行结果映射至Java对象,通过statement中的resultType定义输出结果的类型。
Hibernate是一个标准ORM框架(对象关系映射),对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用Hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
Hibernate入门门槛较高的,不需要程序写SQL,SQL语句自动生成了。但对SQL语句进行优化、修改比较困难的。应用场景:适用与需求变化不多的中小型项目,比如:后台管理系统,ERP、OA…。
MyBatis和hibernate不同,它是一个不完全的ORM框架(虽然程序员自己写SQL,MyBatis也可以实现映射(输入映射、输出映射)),因为MyBatis需要程序员自己编写SQL语句,不过MyBatis可以通过XML或注解方式灵活配置要运行的SQL语句,并将Java对象和SQL语句映射生成最终执行的SQL,最后将SQL执行的结果再映射生成Java对象。
MyBatis专注的是SQL本身,需要程序员自己编写SQL语句,SQL修改、优化比较方便。
MyBatis学习门槛低,简单易学,程序员直接编写原生态SQL,可严格控制SQL执行性能,灵活度高。应用场景:非常适合对关系数据模型要求不高的软件开发,适用于需求变化较多的项目,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是MyBatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套SQL映射文件,工作量大。
企业进行技术选型,以低成本、高回报作为技术选型的原则,根据项目组的技术力量进行选择。总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。
使用Mybatis开发DAO,通常有两个方法,即原始DAO开发方法和Mapper接口开发方法。
将下边的功能实现DAO:
1.根据用户id查询一个用户信息
2.根据用户名称模糊查询用户信息列表
3.添加用户信息
SqlSession中封装了对数据库的操作,如:查询、插入、更新、删除等。
通过SqlSessionFactory创建SqlSession,而SqlSessionFactory是通过SqlSessionFactoryBuilder进行创建。
SqlSessionFactoryBuilder用于创建SqlSessionFacoty,SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory产生,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,不需要使用单例管理SqlSessionFactoryBuilder。在需要创建SqlSessionFactory时候,只需要new一次SqlSessionFactoryBuilder即可。最佳使用范围是方法范围即方法体内局部变量。
SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。
将来MyBatis和Spring整合后,使用单例模式管理SqlSessionFactory。
SqlSession是一个面向用户(程序员)的接口, SqlSession中定义了很多操作数据库的方法,如:selectOne(返回单个对象)、selectList(返回单个或多个对象)…。,默认使用DefaultSqlSession实现类。
SqlSession是线程不安全的,在SqlSession实现类中除了有接口中的方法(操作数据库的方法)还有数据域属性。SqlSession最佳应用场合在方法体内,定义成局部变量使用。
执行过程如下:
1、 加载数据源等配置信息:Environment environment = configuration.getEnvironment();
2、 创建数据库链接
3、 创建事务对象
4、 创建Executor,SqlSession所有操作都是通过Executor完成,MyBatis源码如下:
if (ExecutorType.BATCH == executorType) { executor = newBatchExecutor(this, transaction); } elseif (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor, autoCommit); } |
5、 SqlSession的实现类即DefaultSqlSession,此对象中对操作数据库实质上用的是Executor
结论:每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
打开一个 SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到finally 块中以确保每次都能执行关闭。如下:
SqlSession session = sqlSessionFactory.openSession(); try { // do work } finally { session.close(); } |
思路:原始DAO开发方法需要程序员编写DAO接口和DAO实现类。需要DAO实现类中注入SqlSessionFactory,在方法体内通过SqlSessionFactory创建SqlSession。
同上一章的Users.xml文件。
UserDao.java |
package app.dao; import java.util.List; import app.domain.User; // DAO接口,用户管理 public interface UserDao { // 根据id查询用户信息 public User findUserById(int id)throws Exception; // 根据用户名列查询用户列表 public List<User> findUserByName(Stringname) throws Exception; // 添加用户信息 public void insertUser(Useruser) throws Exception; // 删除用户信息 public void deleteUserById(int id)throws Exception; } |
UserDaoImpl.java |
package app.dao; import java.util.List; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import app.domain.User; //DAO接口实现 public class UserDaoImplimplements UserDao { // 需要向dao实现类中注入SqlSessionFactory // 这里通过构造方法注入 private SqlSessionFactorysqlSessionFactory; public UserDaoImpl(SqlSessionFactorysqlSessionFactory) { this.sqlSessionFactory =sqlSessionFactory; } /* * // 使用JDK 7之前的try-catch-finally语法 * * @Override public User findUserById(int id) throws Exception { SqlSession * sqlSession = sqlSessionFactory.openSession(); * * User user = sqlSession.selectOne("test.findUserById", id); * * // 释放资源 sqlSession.close(); * * return user; * * } */
// 使用JDK 7之后的try-with-resources语法 @Override public User findUserById(int id)throws Exception { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { User user = sqlSession.selectOne("test.findUserById",id); return user; } } @Override public List<User> findUserByName(Stringname) throws Exception { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { List<User> list = sqlSession.selectList("test.findUserByName",name); return list; } } @Override public void insertUser(Useruser) throws Exception { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { sqlSession.insert("test.insertUser",user);//执行插入操作 sqlSession.commit();//提交事务 } } @Override public void deleteUserById(int id)throws Exception { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { sqlSession.delete("test.deleteUserById",id);//执行删除操作 sqlSession.commit();//提交事务 } } } |
UserDaoImpl.java |
package app.test.dao; import java.io.InputStream; import java.util.Date; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import app.dao.UserDao; import app.dao.UserDaoImpl; import app.domain.User; public class UserDaoImplTest { private SqlSessionFactorysqlSessionFactory; // 此方法是在执行testFindUserById之前执行 @Before public void setUp()throws Exception { // 创建sqlSessionFactory String resource = "SqlMapConfig.xml";// mybatis配置文件 InputStream inputStream = Resources.getResourceAsStream(resource);//得到配置文件流 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//创建会话工厂,传入mybatis的配置文件信息 } @Test public void testFindUserById()throws Exception { UserDao userDao = new UserDaoImpl(sqlSessionFactory);//创建UserDao的对象 User user = userDao.findUserById(1);// 调用UserDao的方法 System.out.println(user); } @Test public void testFindUserByName()throws Exception { UserDao userDao = new UserDaoImpl(sqlSessionFactory);//创建UserDao的对象 List<User> users = userDao.findUserByName("小明");//调用UserDao的方法 System.out.println(users); } @Test public void testInsertUser()throws Exception { // 插入用户对象 User user = new User(); user.setUsername("王小军"); user.setBirthday(new Date()); user.setSex("1"); user.setAddress("河南郑州"); UserDao userDao = new UserDaoImpl(sqlSessionFactory);//创建UserDao的对象 userDao.insertUser(user);//调用UserDao的方法 } @Test public void testDeleteUserById()throws Exception { UserDao userDao = new UserDaoImpl(sqlSessionFactory);//创建UserDao的对象 userDao.deleteUserById(32);//调用UserDao的方法 } } |
原始Dao开发中存在以下问题:
1、DAO接口实现类方法中存在大量的重复模板方法:通过SqlSessionFactory创建SqlSession,调用SqlSession的数据库操作方法,设想能否将这些代码提取出来,大大减轻程序员的工作量。
2、调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不利于开发维护。
3、调用sqlSession方法时传入的变量,由于sqlSession方法使用Object泛型(int org.apache.ibatis.session.SqlSession.insert(String arg0,Object arg1)),即使变量类型传入错误,在编译阶段也不报错,不利于程序员开发。
Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由MyBatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
程序员需要编写mapper.xml映射文件。程序员编写mapper接口需要遵循一些开发规范,MyBatis就可以自动生成mapper接口实现类代理对象。
Mapper接口开发需要遵循以下规范:
1、 XxxMapper.xml文件中的namespace与XxxMapper接口的全限定名相同。
package app.mapper; // … public interface UserMapper { |
<!-- namespace命名空间,作用就是对sql进行分类化管理,理解sql隔离 注意:使用mapper代理方法开发,namespace有特殊重要的作用,namespace等于mapper接口的全限定名 --> <mapper namespace="app.mapper.UserMapper"> |
2、 XxxMapper接口方法名和XxxMapper.xml中定义的每个statement的id相同
// 根据id查询用户信息 public User findUserById(int id)throws Exception; |
<select id="findUserById" parameterType="int" resultType="app.domain.User"> SELECT * FROM USER WHERE id=#{value} </select> |
3、 XxxMapper接口方法的输入参数类型和XxxMapper.xml中定义的每个SQL的parameterType的类型相同
// 根据id查询用户信息 public User findUserById(int id)throws Exception; |
<select id="findUserById" parameterType="int" resultType="app.domain.User"> SELECT * FROM USER WHERE id=#{value} </select> |
4、 XxxMapper接口方法的返回值类型和XxxMapper.xml中定义的每个SQL的resultType的类型相同
// 根据id查询用户信息 public User findUserById(int id)throws Exception; |
<select id="findUserById" parameterType="int" resultType="app.domain.User"> SELECT * FROM USER WHERE id=#{value} </select> |
总结:以上开发规范主要是对下边的代码进行统一生成:
User user = sqlSession.selectOne("test.findUserById", id); sqlSession.insert("test.insertUser", user); 。。。。 |
在src下的app.mapper包中创建Mapper接口文件:UserMapper.java(内容同UserDao.java)。
UserMapper.java |
package app.mapper; import java.util.List; import app.domain.User; // 用户管理Mapper public interface UserMapper { // 根据id查询用户信息 public User findUserById(int id)throws Exception; // 根据用户名列查询用户列表 public List<User> findUserByName(Stringname) throws Exception; // 添加用户信息 public void insertUser(Useruser) throws Exception; // 删除用户信息 public void deleteUserById(int id)throws Exception; } |
接口定义有如下特点:
1、 Mapper接口方法名和Mapper.xml中定义的statement的id相同
2、 Mapper接口方法的输入参数类型和mapper.xml中定义的statement的parameterType的类型相同
3、 Mapper接口方法的输出参数类型和mapper.xml中定义的statement的resultType的类型相同
在conf文件夹下新建一个文件夹mapper,然后在它下面创建UserMapper.xml文件。
定义Mapper映射文件UserMapper.xml(内容同Users.xml),需要修改namespace的值为UserMapper接口路径。将UserMapper.xml放在classpath下mapper目录下。
UserMapper.xml |
<?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"> <!-- namespace命名空间,作用就是对sql进行分类化管理,理解sql隔离 注意:使用mapper代理方法开发,namespace有特殊重要的作用,namespace等于mapper接口的全限定名 --> <mapper namespace="app.mapper.UserMapper"> <select id="findUserById" parameterType="int" resultType="app.domain.User"> SELECT * FROM USER WHERE id=#{value} </select> <select id="findUserByName" parameterType="java.lang.String" resultType="app.domain.User"> SELECT * FROM USER WHERE username LIKE ‘%${value}%‘ </select> <insert id="insertUser" parameterType="app.domain.User"> <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> SELECT LAST_INSERT_ID() </selectKey> insert into user(username,birthday,sex,address) value(#{username},#{birthday},#{sex},#{address}) </insert> <delete id="deleteUserById" parameterType="java.lang.Integer"> delete from user where id=#{id} </delete> <update id="updateUser" parameterType="app.domain.User"> update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id} </update> </mapper> |
修改SqlMapConfig.xml文件:
SqlMapConfig.xml |
<mappers> <mapper resource="sqlmap/User.xml"/> <mapper resource="mapper/UserMapper.xml"/> </mappers> |
UserMapperlTest.java |
package app.test.mapper; import java.io.InputStream; import java.util.Date; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import app.dao.UserDao; import app.dao.UserDaoImpl; import app.domain.User; import app.mapper.UserMapper; public class UserMapperlTest { private SqlSessionFactorysqlSessionFactory; // 此方法是在执行testFindUserById之前执行 @Before public void setUp()throws Exception { // 创建sqlSessionFactory String resource = "SqlMapConfig.xml";// mybatis配置文件 InputStream inputStream = Resources.getResourceAsStream(resource);//得到配置文件流 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//创建会话工厂,传入mybatis的配置文件信息 } @Test public void testFindUserById()throws Exception { try (SqlSessionsqlSession = sqlSessionFactory.openSession()) { // 创建UserMapper对象,mybatis自动生成mapper代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 调用userMapper的方法 User user = userMapper.findUserById(1); System.out.println(user); } } @Test public void testFindUserByName()throws Exception { try (SqlSessionsqlSession = sqlSessionFactory.openSession()) { // 创建UserMapper对象,mybatis自动生成mapper代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 调用userMapper的方法 List<User> list = userMapper.findUserByName("小明"); System.out.println(list); } } @Test public void testInsertUser()throws Exception { // 插入用户对象 User user = new User(); user.setUsername("王小军"); user.setBirthday(new Date()); user.setSex("1"); user.setAddress("河南郑州"); try (SqlSessionsqlSession = sqlSessionFactory.openSession()) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); userMapper.insertUser(user); } } @Test public void testDeleteUserById()throws Exception { try (SqlSessionsqlSession = sqlSessionFactory.openSession()) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); userMapper.deleteUserById(36); } } } |
u selectOne()和selectList():
动态代理对象调用sqlSession.selectOne()和sqlSession.selectList()是根据mapper接口方法的返回值决定,如果返回list则调用selectList方法,如果返回单个对象则调用selectOne方法。
u namespace
MyBatis官方推荐使用mapper代理方法开发mapper接口,程序员不用编写mapper接口实现类,使用mapper代理方法时,输入参数可以使用POJO包装对象或Map对象,保证DAO的通用性。
系统框架中,DAO层的代码是被业务层公用的。即使mapper接口只有一个参数,可以使用包装类型的POJO满足不同的业务方法的需求。
注意:持久层方法的参数可以用包装类型、map。。。,service方法中建议不要使用包装类型(不利于业务层的扩展)。
SqlMapConfig.xml中配置的内容和顺序如下:
properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
需求:将数据库连接参数单独配置在db.properties中,只需要在SqlMapConfig.xml中加载db.properties的属性值。在SqlMapConfig.xml中就不需要对数据库连接参数硬编码。
将数据库连接参数只配置在db.properties中,原因:方便对参数进行统一管理,其它xml可以引用该db.properties。
1、在classpath(conf目录)下定义db.properties文件。
db.properties |
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis jdbc.username=root jdbc.password=root |
2、在SqlMapConfig.xml加载属性文件。
<properties resource="db.properties" />
3、SqlMapConfig.xml引用Java属性文件中的配置信息如下:
SqlMapConfig.xml |
<!-- 加载属性文件 --> <properties resource="db.properties" /> <environments default="development"> <environment id="development"> <!-- 使用jdbc事务管理--> <transactionManager type="JDBC" /> <!-- 配置数据库连接信息--> <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> |
properties特性:
注意:MyBatis将按照下面的顺序来加载属性:
u 在properties元素体内定义的属性首先被读取。
u 然后会读取properties元素中resource或url加载的属性,它会覆盖已读取的同名属性。
u 最后读取parameterType传递的属性,它会覆盖已读取的同名属性。
因此,通过parameterType传递的属性具有最高优先级,resource或url 加载的属性次之,最低优先级的是properties元素体内定义的属性。
建议:
不要在properties元素体内添加任何属性值,只将属性值定义在properties文件中。
在properties文件中定义属性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX
MyBatis框架在运行时可以调整一些运行参数。MyBatis全局配置参数,全局参数将会影响MyBatis的运行行为。比如:开启二级缓存、开启延迟加载。详细参见“MyBatis用户手册”3.2 settings节。
不要随意设置这些参数,设置不当就好会影响性能。
类型别名是在SqlMapConfig.xml中为Java类型设置一个短的名字,在mapper.xml使用短名字。
在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。如果在指定类型时输入类型完全限定名,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。
详细参见“MyBatis用户手册”3.3 typeAliases节。
1、定义单个别名
SqlMapConfig.xml |
<!-- 别名定义 --> <typeAliases> <!-- 针对单个别名定义 type:类型的完全限定名 alias:别名 --> <typeAlias type="app.domain.User" alias="user"/> </typeAliases> |
2、引用别名:
UserMapper.xml |
<!-- <select id="findUserById" parameterType="int" resultType="app.domain.User"> SELECT * FROM USER WHERE id=#{value} </select> --> <!-- 别名可用User或user --> <select id="findUserById" parameterType="int" resultType="user"> SELECT * FROM USER WHERE id=#{value} </select> |
如果别名写错了,比如写成user1,会报以下异常:。
Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias ‘user1‘.
SqlMapConfig.xml |
<!-- 别名定义 --> <typeAliases> <!-- 批量别名定义 指定包名,MyBatis自动扫描包中的po类,自动定义别名,别名就是类名(首字母大写或小写都可以,一般小写) --> <package name="app.domain"/> </typeAliases> |
无论是MyBatis在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成Java类型。
类型处理器用于Java类型和JDBC类型映射,如下:
<select id="findUserById" parameterType="int" resultType="user"> select * from user where id = #{id} </select> |
MyBatis自带的类型处理器基本上满足日常需求,不需要单独定义。MyBatis支持类型处理器详细参见“MyBatis用户手册”3.5 typeHandlers节。
详细参见“MyBatis用户手册”3.4 处理枚举类型节。
Mapper配置的几种方法:
使用相对于类路径的资源。如:<mapper resource="sqlmap/User.xml" />
<!--通过resource方法一次加载一个映射文件--> <mapper resource="mapper/UserMapper.xml"/> |
使用完全限定路径。
如:<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />
使用mapper接口的完全限定名。
如:<mapper class="app.mapper.UserMapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
1、按照上边的规范,将mapper.java和mapper.xml放在一个目录 ,且同名。
2、配置mapper
<!-- 通过mapper接口加载单个映射文件 遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录中 上边规范的前提是:使用的是mapper代理方法 --> <mapper class="app.mapper.UserMapper"/> |
注册指定包下的所有mapper接口。
如:<package name="app.mapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
<!-- 批量加载mapper 指定mapper接口的包名,MyBatis自动扫描包下边所有mapper接口进行加载 遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录中 上边规范的前提是:使用的是mapper代理方法 --> <package name="app.mapper"/> |
Mapper.xml映射文件中定义了操作数据库的SQL,每个SQL是一个statement,映射文件是MyBatis的核心。
在statement中,通过parameterType指定输入参数的类型,类型可以是简单类型、hashmap、POJO的包装类型。
#{}实现的是向prepareStatement中的预处理语句中设置参数值,SQL语句中#{}表示一个占位符即?。
<!-- 根据id查询用户信息--> <select id="findUserById" parameterType="int" resultType="user"> select * from user where id = #{id} </select> |
使用占位符#{}可以有效防止SQL注入,在使用时不需要关心参数值的类型,MyBatis会自动进行Java类型和JDBC类型的转换。#{}可以接收简单类型值或POJO的属性值,如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
${}和#{}不同,通过${}可以将parameterType传入的内容拼接在SQL中且不进行JDBC类型转换,${}可以接收简单类型值或POJO的属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。使用${}不能防止sql注入,但是有时用${}会非常方便,如下的例子:
<!-- 根据名称模糊查询用户信息--> <select id="selectUserByName" parameterType="string" resultType="user"> select * from user where username like ‘%${value}%‘ </select> |
如果本例子使用#{}则传入的字符串中必须有%号,而%是人为拼接在参数中,显然有点麻烦,如果采用${}在sql中拼接为%的方式则在调用mapper接口传递参数就方便很多。
//如果使用占位符号则必须人为在传参数中加% List<User> list = userMapper.selectUserByName("%管理员%");
//如果使用${}原始符号则不用人为在参数中加% List<User>list = userMapper.selectUserByName("管理员"); |
再比如order by排序,如果将列名通过参数传入SQL,根据传的列名进行排序,应该写为:
<!-- 根据列名排序 --> <select id="selectUserOrderBy" parameterType="string"resultType="user"> SELECT * FROM USER ORDER BY ${value} </select> |
执行结果:
上例中如果使用#{}将无法实现此功能,因为#{}会转换为字符串,例如‘username‘,无法正确排序。
执行结果:
参考上边的例子。
MyBatis使用ognl表达式解析对象字段的值,如下例子:
<!-- 传递POJO对象综合查询用户信息--> <select id="findUserByUser" parameterType="user" resultType="user"> SELECT * FROM user WHERE id=#{id} AND username LIKE ‘%${username}%‘ </select> |
上边红色标注的是user对象中的字段名称。
测试:
@Test public void testFindUserByUser()throws Exception { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { User user = new User(); user.setId(2); user.setUsername("小军"); // 创建UserMapper对象,mybatis自动生成mapper代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 调用userMapper的方法 List<User> list = userMapper.findUserByUser(user); System.out.println(list); } } |
异常测试:SQL中字段名输入错误后测试,username输入username1测试结果报错:
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.apache.ibatis.reflection.ReflectionException:There is no getter for property named ‘username1‘ in ‘class app.domain.User‘
### Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named ‘username1‘ in ‘class app.domain.User‘
开发中通过POJO传递查询条件,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
完成用户信息的综合查询,需要传入查询条件很复杂(可能包括用户信息、其它信息,比如商品、订单的)。
自定义用户扩展类。针对上边需求,建议使用自定义的包装类型的POJO。在包装类型的POJO中将复杂的查询条件包装进去。定义包装对象将查询条件(POJO)以类组合的方式包装起来。Vo表示View Object(表示层对象)。
UserQueryVo.java |
package app.domain; import java.util.List; //包装类型 public class UserQueryVo { private List<Integer>ids; //传入多个id //在这里包装所需要的查询条件 private UserCustom userCustom; //用户查询条件 //可以包装其它的查询条件,订单、商品 //.... // Getter、Setter } |
由于User类一般是逆向工程生成的,最好不要修改它,而是扩展它,这样更灵活。
UserCustom.java |
package app.domain; //用户的扩展类 public class UserCustomextends User{ //可以扩展用户的信息 } |
UserMapper.java |
//用户信息综合查询 public List<UserCustom> findUserList(UserQueryVo userQueryVo)throws Exception; |
在UserMapper.xml中定义用户信息综合查询(查询条件复杂,通过高级查询进行复杂关联查询)。
UserMapper.xml |
<!-- 用户信息综合查询 --> <select id="findUserList" parameterType="userQueryVo" resultType="UserCustom"> SELECT * FROM USER WHERE sex=#{userCustom.sex} AND username LIKE %${userCustom.username}%‘ </select> |
上边红色标注的是包装对象userQueryVo对象中的字段名称。
说明:MyBatis底层通过OGNL从POJO中获取属性值:#{userCustom.username},userCustom即是传入的包装对象的属性。userQueryVo是别名,即上边定义的包装对象类型。
UserMapperlTest.java |
@Test public void testFindUserList()throws Exception { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserCustom userCustom = new UserCustom(); userCustom.setSex("1"); userCustom.setUsername("小明"); UserQueryVo userQueryVo = new UserQueryVo(); userQueryVo.setUserCustom(userCustom); List<UserCustom> list = userMapper.findUserList(userQueryVo); System.out.println(list); } } |
UserMapper.java |
//根据hashmap查询用户列表 public List<UserCustom> findUserByHashMap(HashMap<String, Object> hashmap)throws Exception; |
Sql映射文件定义如下:
UserMapper.xml |
<!-- 传递hashmap综合查询用户信息--> <select id="findUserByHashMap" parameterType="hashmap" resultType="user"> select * from user where id=#{id} and username like ‘%${username}%‘ </select> |
上边红色标注的是hashmap的key。
UserMapperlTest.java |
@Test public void testFindUserByHashMap()throws Exception{ try(SqlSession session = sqlSessionFactory.openSession()){ UserMapper userMapper = session.getMapper(UserMapper.class); //构造查询条件HashMap对象 HashMap<String, Object> map =new HashMap<String, Object>(); map.put("id", 28); map.put("username","王"); //传递HashMap对象查询用户列表 List<UserCustom> list = userMapper.findUserByHashMap(map); System.out.println(list); } } |
异常测试:传递的map中的key和sql中解析的key不一致。测试结果没有报错,只是通过key获取的值为空。
用户信息的综合查询列表总数,通过查询总数和上边用户综合查询列表才可以实现分页。
参考getnow输出日期类型(select CURRENT_TIMESTAMP()、select CURRENT_DATE()),看下边的例子输出整型:
Mapper.xml文件
<!-- 获取用户列表总数 --> <select id="findUserCount" parameterType="user" resultType="int"> SELECT count(*) FROM USER WHERE sex=#{userCustom.sex} AND username LIKE ‘%${userCustom.username}%‘ </select> |
Mapper接口
//获取用户列表总数 public int findUserCount(UserQueryVouserQueryVo) throws Exception; |
调用:
@Test public void testFindUserCount()throws Exception{ try(SqlSession session = sqlSessionFactory.openSession()){ //创建UserMapper对象,MyBatis自动生成mapper代理对象 UserMapper userMapper = session.getMapper(UserMapper.class); //创建包装对象,设置查询条件 UserQueryVo userQueryVo = new UserQueryVo(); UserCustom userCustom = new UserCustom(); userCustom.setSex("1"); userCustom.setUsername("小明"); userQueryVo.setUserCustom(userCustom); //调用userMapper的方法 int count =userMapper.findUserCount(userQueryVo); System.out.println(count); } } |
总结:
查询出来的结果集只有一行且一列,可以使用简单类型进行输出映射。
输出简单类型查询出来的结果集必须只有一条记录,最终将第一个字段的值转换为输出类型。使用session的selectOne可查询单条记录。
使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
如果查询出来的列名和POJO中的属性名全部不一致,没有创建pojo对象。
只要查询出来的列名和POJO中的属性有一个一致,就会创建pojo对象。
参考findUserById的定义:
Mapper.xml
<!-- 根据id查询用户信息--> <select id="findUserById" parameterType="int" resultType="user"> select * from user where id = #{id} </select> |
Mapper接口:
public User findUserById(int id) throws Exception; |
测试:
@Test public void testFindUserById()throws Exception { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { // 创建UserMapper对象,MyBatis自动生成mapper代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 调用userMapper的方法,通过mapper接口调用statement User user = userMapper.findUserById(1); System.out.println(user); } } |
使用session调用selectOne查询单条记录。
参考selectUserByName的定义:
Mapper.xml
<!-- 根据名称模糊查询用户信息--> <select id="findUserByName" parameterType="string" resultType="user"> select * from user where username like ‘%${value}%‘ </select> |
Mapper接口:
public List<User> findUserByName(String username) throws Exception; |
测试:
Public void testFindUserByName()throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //如果使用占位符号则必须人为在传参数中加% //List<User> list = userMapper.selectUserByName("%管理员%"); //如果使用${}原始符号则不用人为在参数中加% List<User> list = userMapper.findUserByName("管理员"); //关闭session session.close(); } |
使用session的selectList方法获取POJO列表。
输出POJO对象可以改用hashmap输出类型,将输出的字段名称作为map的key,value为字段值。
输出POJO对象和输出POJO列表在SQL中定义的resultType是一样的。
返回单个POJO对象要保证SQL查询出来的结果集为单条,内部使用session.selectOne方法调用,mapper接口使用POJO对象作为方法返回值。
返回POJO列表表示查询出来的结果集可能为多条,内部使用session.selectList方法,mapper接口使用List<POJO>对象作为方法返回值。
MyBatis中使用resultMap完成高级输出结果映射。
resultType可以指定POJO将查询结果映射为POJO,但需要POJO的属性名和SQL查询的列名一致方可映射成功。当实体类中的属性名和表中的字段名不一致时,使用MyBatis进行查询操作时无法查询出相应的结果的问题以及针对问题采用的两种办法:
解决办法一:通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致,这样就可以表的字段名和实体类的属性名一一对应上了,这种方式是通过在sql语句中定义别名来解决字段名和属性名的映射关系的。
<!-- 根据id查询得到一个order对象,使用这个查询是可以正常查询到我们想要的结果的,这是因为我们将查询的字段名都起一个和实体类属性名相同的别名,这样实体类的属性名和查询结果中的字段名就可以一一对应上。 如果Order实体类中属性名和orders表中的字段名是不一样的:id=>order_id、orderNo=>order_no、price=>order_price --> <selectid="selectOrder" parameterType="int" resultType="app.domain.Order"> select order_id id,order_no orderNo,order_price price from orders where order_id=#{id} </select> |
解决办法二:通过<resultMap>来映射字段名和实体类属性名的一一对应关系。这种方式是使用MyBatis提供的解决方式来解决字段名和属性名的映射关系的。
如果SQL查询字段名和POJO的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系,resultMap实质上还需要将查询结果映射到POJO对象中。
resultMap可以实现将查询结果映射为复杂类型的POJO,比如在查询结果映射对象中包括POJO和list实现一对一查询和一对多查询。
如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。
1、定义resultMap
2、使用resultMap作为statement的输出映射类型
由于上边的mapper.xml中sql查询列和Users.java类属性不一致,需要定义resultMap:userResultMap将sql查询列和Users.java类属性对应起来。
UserMapper.xml |
<!-- 定义resultMap 将SELECT id id_,username username_ FROM USER和User类中的属性作一个映射关系 type:resultMap最终映射的java对象类型,可以使用别名 id:对resultMap的唯一标识 --> <resultMap type="user" id="userResultMap"> <!-- id表示查询结果集中唯一标识 column:查询出来的列名 property:type指定的pojo类型中的属性名 最终resultMap对column和property作一个映射关系 (对应关系) --> <id column="id_" property="id"/> <!-- result:对普通名映射定义 column:查询出来的列名 property:type指定的pojo类型中的属性名 最终resultMap对column和property作一个映射关系 (对应关系) --> <result column="username_" property="username"/> </resultMap> |
<id />:此属性表示查询结果集的唯一标识,非常重要。如果是多个字段为复合唯一约束则定义多个<id />。
Property:表示person类的属性。
Column:表示sql查询出来的字段名。
Column和property放在一块儿表示将sql查询出来的字段映射到指定的POJO类属性上。
<result />:普通结果,即POJO的属性。
ResultMap标签
1. 属性说明
id属性 ,resultMap标签的标识。
type属性 ,返回值的全限定类名,或类型别名。
autoMapping属性 ,值范围true(默认值)|false,设置是否启动自动映射功能,自动映射功能就是自动查找与字段名小写同名的属性名,并调用setter方法。而设置为false后,则需要在`resultMap`内明确注明映射关系才会调用对应的setter方法。
2. 基本作用:建立SQL查询结果字段与实体属性的映射关系信息
子元素说明:
id元素 ,用于设置主键字段与领域模型属性的映射关系
result元素 ,用于设置普通字段与领域模型属性的映射关系
constructor元素 ,指定使用指定参数列表的构造函数来实例化领域模型。注意:其子元素顺序必须与参数列表顺序对应
idArg子元素 ,标记该入参为主键
arg子元素 ,标记该入参为普通字段(主键使用该子元素设置也是可以的)
id元素,result元素,idArg元素,arg元素,discriminator元素的共同属性
javaType属性 :Java类的全限定名,或别名
jdbcType属性 :JDBC类型, JDBC类型为CUD操作时列可能为空时进行处理
typeHandler属性 :指定类型处理器的全限定类名或类型别名
column属性 :指定SQL查询结果的字段名或字段别名。将用于JDBC的resultSet.getString(columnName)
将下边的sql使用User完成映射:
SELECT id id_,username username_ FROM USER WHERE id=#{value}
User类中属性名和上边查询列名不一致。
UserMapper.xml |
<!-- 使用resultMap进行输出映射 resultMap:指定定义的resultMap的id,如果这个resultMap在其它的mapper文件,前边需要加namespace --> <select id="findUserByIdResultMap" parameterType="int" resultMap="userResultMap"> SELECT id id_,username username_ FROM USER WHERE id=#{value} </select> |
使用resultMap指定上边定义的userResultMap。
//根据id查询用户信息,使用resultMap输出 public User findUserByIdResultMap(int id)throws Exception; |
通过MyBatis提供的各种标签方法实现动态拼接sql。MyBatis核心:对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接、组装。
用户信息综合查询列表和用户信息查询列表总数这两个statement的定义使用动态sql。
对查询条件进行判断,如果输入参数不为空才进行查询条件拼接。
<!-- 传递POJO综合查询用户信息--> <select id="findUserList" parameterType="user" resultType="user"> select * from user where 1=1 <if test="id!=null and id!=‘‘"> and id=#{id} </if> <if test="username!=null and username!=‘‘"> and username like ‘%${username}%‘ </if> </select> |
注意要做不等于空字符串校验。
上边的sql也可以改为:
<select id="findUserList" parameterType="user" resultType="user"> select * from user <where> <if test="id!=null and id!=‘‘"> and id=#{id} </if> <if test="username!=null and username!=‘‘"> and username like ‘%${username}%‘ </if> </where> </select> |
<where />可以自动处理第一个and。
向sql传递数组或List,MyBatis使用foreach解析,如下:
u 需求
传入多个id查询用户信息,用下边两个sql实现:
SELECT * FROM USERS WHERE username LIKE ‘%张%‘ AND (id =10 OR id =89 OR id=16)
SELECT * FROM USERS WHERE username LIKE ‘%张%‘ id IN (10,89,16)
u 在POJO中定义list属性ids存储多个用户id,并添加getter/setter方法
u mapper.xml
<if test="ids!=null and ids.size>0"> <!-- 使用 foreach遍历传入ids collection:指定输入 对象中集合属性 item:每个遍历生成对象中 open:开始遍历时拼接的串 close:结束遍历时拼接的串 separator:遍历的两个对象中需要拼接的串 --> <!-- 使用实现下边的sql拼接: AND (id=1 OR id=10 OR id=16) --> <foreach collection="ids" item="user_id" open="AND (" close=")" separator="or"> <!-- 每个遍历需要拼接的串--> id=#{user_id} </foreach> <!-- 实现 “ and id IN(1,10,16)”拼接--> <!-- <foreach collection="ids" item="user_id" open="and id IN(" close=")" separator=","> 每个遍历需要拼接的串 #{user_id} </foreach> --> </if> |
u 测试代码:
//创建UserMapper对象,mybatis自动生成mapper代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //创建包装对象,设置查询条件 UserQueryVo userQueryVo = new UserQueryVo(); UserCustom userCustom = new UserCustom(); //由于这里使用动态sql,如果不设置某个值,条件不会拼接在sql中 //userCustom.setSex("1"); userCustom.setUsername("小明"); //传入多个id List<Integer> ids = new ArrayList<Integer>(); ids.add(1); ids.add(10); ids.add(16); //将ids通过userQueryVo传入statement中 userQueryVo.setIds(ids); userQueryVo.setUserCustom(userCustom); //调用userMapper的方法 List<UserCustom> list = userMapper.findUserList(userQueryVo); System.out.println(list); |
传递List类型在编写mapper.xml没有区别,唯一不同的是只有一个List参数时它的参数名为list。
如下:
u Mapper.xml
<select id="selectUserByList" parameterType="java.util.List" resultType="user"> select * from user <where> <!-- 传递List,List中是POJO --> <if test="list!=null"> <foreach collection="list" item="item" open="and id in("separator=","close=")"> #{item.id} </foreach> </if> </where> </select> |
u Mapper接口
public List<User> selectUserByList(List userlist) throws Exception; |
u 测试:
public void testselectUserByList()throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //构造查询条件List List<User> userlist = new ArrayList<User>(); User user = new User(); user.setId(1); userlist.add(user); user = new User(); user.setId(2); userlist.add(user); //传递userlist列表查询用户列表 List<User>list = userMapper.selectUserByList(userlist); //关闭session session.close(); } |
请阅读文档学习。
u Mapper.xml
<!-- 传递数组综合查询用户信息--> <select id="selectUserByArray" parameterType="Object[]" resultType="user"> select * from user <where> <!-- 传递数组 --> <if test="array!=null"> <foreach collection="array" index="index" item="item" open="and id in("separator=","close=")">#{item.id} </foreach> </if> </where> </select> |
sql只接收一个数组参数,这时sql解析参数的名称mybatis固定为array,如果数组是通过一个POJO传递到sql则参数的名称为POJO中的属性名。
index:为数组的下标。
item:为数组每个元素的名称,名称随意定义
open:循环开始
close:循环结束
separator:中间分隔输出
u Mapper接口:
public List<User> selectUserByArray(Object[] userlist) throws Exception;
u 测试:
public void testselectUserByArray()throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //构造查询条件List Object[] userlist = new Object[2]; User user = new User(); user.setId(1); userlist[0]=user; user = new User(); user.setId(2); userlist[1]=user; //传递user对象查询用户列表 List<User>list = userMapper.selectUserByArray(userlist); //关闭session session.close(); } |
请阅读文档学习。
u Mapper.xml
<!-- 传递数组综合查询用户信息--> <select id="selectUserByArray" parameterType="Object[]" resultType="user"> select * from user <where> <!-- 传递数组 --> <if test="array!=null"> <foreach collection="array"index="index"item="item"open="and id in("separator=","close=")"> #{item} </foreach> </if> </where> </select> |
如果数组中是简单类型则写为#{item},不用再通过ognl获取对象属性值了。
u Mapper接口:
public List<User> selectUserByArray(Object[] userlist) throws Exception;
u 测试:
public void testselectUserByArray()throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //构造查询条件List Object[] userlist = new Object[2]; userlist[0]=”1”; userlist[1]=”2”; //传递user对象查询用户列表 List<User>list = userMapper.selectUserByArray(userlist); //关闭session session.close(); } |
SQL中可将重复的SQL提取出来,使用时用include引用即可,最终达到SQL重用的目的,如下:
<!-- 传递POJO综合查询用户信息--> <select id="findUserList" parameterType="user" resultType="user"> select * from user <where> <if test="id!=null and id!=‘‘"> and id=#{id} </if> <if test="username!=null and username!=‘‘"> and username like ‘%${username}%‘ </if> </where> </select> |
u 将where条件抽取出来:
<sql id="query_user_where"> <if test="id!=null and id!=‘‘"> and id=#{id} </if> <if test="username!=null and username!=‘‘"> and username like ‘%${username}%‘ </if> </sql> |
u 使用include引用:
<select id="findUserList" parameterType="user" resultType="user"> select * from user <where> <include refid="query_user_where"/> </where> </select> |
注意:如果引用其它mapper.xml的sql片段,则在引用时需要加上namespace,如下:
<include refid="namespace.sql片段”/>
将上边实现的动态sql判断代码块抽取出来,组成一个sql片段。其它的statement中就可以引用sql片段。方便程序员进行开发。
XxxMapper.xml |
<!-- 定义sql片段 id:sql片段的唯一标识 经验:是基于单表来定义sql片段,这样话这个sql片段可重用性才高。 在sql片段中不要包括where --> <sql id="query_user_where"> <if test="userCustom!=null"> <if test="userCustom.sex!=null and userCustom.sex!=‘‘"> and user.sex = #{userCustom.sex} </if> <if test="userCustom.username!=null and userCustom.username!=‘‘"> and user.username LIKE ‘%${userCustom.username}%‘ </if> <if test="ids!=null"> <!-- 使用 foreach遍历传入ids collection:指定输入 对象中集合属性 item:每个遍历生成对象中 open:开始遍历时拼接的串 close:结束遍历时拼接的串 separator:遍历的两个对象中需要拼接的串 --> <!-- 使用实现下边的sql拼接: AND (id=1 OR id=10 OR id=16) --> <foreach collection="ids" item="user_id" open="AND (" close=")" separator="or"> <!-- 每个遍历需要拼接的串--> id=#{user_id} </foreach> <!-- 实现 “ and id IN(1,10,16)”拼接--> <!-- <foreach collection="ids" item="user_id" open="and id IN(" close=")" separator=","> 每个遍历需要拼接的串 #{user_id} </foreach> --> </if> </if> </sql> |
在mapper.xml中定义的statement中引用sql片段:
<!-- 用户信息综合查询 #{userCustom.sex}:取出pojo包装对象中性别值 ${userCustom.username}:取出pojo包装对象中用户名称 --> <select id="findUserList" parameterType="app.po.UserQueryVo" resultType="app.po.UserCustom"> SELECT * FROM USER <!-- where可以自动去掉条件中的第一个and --> <where> <!-- 引用sql片段 的id,如果refid指定的id不在本mapper文件中,需要前边加namespace --> <include refid="query_user_where"></include> <!-- 在这里还要引用其它的sql片段 --> </where> </select> |
<!-- 用户信息综合查询总数 parameterType:指定输入类型和findUserList一样 resultType:输出结果类型 --> <select id="findUserCount" parameterType="app.po.UserQueryVo" resultType="int"> SELECT count(*) FROM USER <!-- where可以自动去掉条件中的第一个and --> <where> <!-- 引用sql片段 的id,如果refid指定的id不在本mapper文件中,需要前边加namespace --> <include refid="query_user_where"></include> <!-- 在这里还要引用其它的sql片段 --> </where> </select> |
课程安排:
对订单商品数据模型进行分析。
高级映射:(了解)
实现一对一查询、一对多、多对多查询。
延迟加载
查询缓存
一级缓存
二级缓存(了解mybatis二级缓存使用场景)
mybatis和spirng整合(掌握)
逆向工程(会用)
1、每张表记录的数据内容
分模块对每张表记录的内容进行熟悉,相当于你学习系统需求(功能)的过程。
2、每张表重要的字段设置
非空字段、外键字段
3、数据库级别表与表之间的关系
外键关系
4、表与表之间的业务关系
在分析表与表之间的业务关系时一定要建立在某个业务意义基础上去分析。
用户表user:记录了购买商品的用户信息。
订单表order:记录了用户所创建的订单(购买商品的订单)。
订单明细表orderdetail:记录了订单的详细信息,即购买商品的信息。
商品表items:记录了商品信息。
表与表之间的业务关系:
在分析表与表之间的业务关系时需要建立在某个业务意义基础上去分析。
①先分析数据级别之间有关系的表之间的业务关系:
usre和orders:
useràorders:一个用户可以创建多个订单,一对多。
ordersàuser:一个订单只由一个用户创建,一对一。
orders和orderdetail:
ordersàorderdetail:一个订单可以包括多个订单明细,因为一个订单可以购买多个商品,每个商品的购买信息在orderdetail记录,一对多关系。
orderdetailà orders:一个订单明细只能包括在一个订单中,一对一。
orderdetail和items:
orderdetailàitems:一个订单明细只对应一个商品信息,一对一
itemsà orderdetail:一个商品可以包括在多个订单明细 ,一对多
②再分析数据库级别没有关系的表之间是否有业务关系:
orders和items:orders和items之间可以通过orderdetail表建立关系。
查询所有订单信息,关联查询下单用户信息。
注意:因为一个订单信息只会是一个人下的订单,所以从查询订单信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的订单信息则为一对多查询,因为一个用户可以下多个订单。
使用resultType,定义订单信息PO类,此PO类中包括了订单信息和用户信息:
确定查询的主表:订单表
确定查询的关联表:用户表
关联查询使用内连接?还是外连接?由于orders表中有一个外键(user_id),通过外键关联查询用户表只能查询出一条记录,可以使用内连接。
SELECT orders.*, user.username, userss.address FROM orders, WHERE orders.user_id = user.id
将上边SQL查询的结果映射到POJO中,POJO中必须包括所有查询列名。
原始的Orders.java不能映射全部字段,需要新创建的POJO。创建一个POJO继承包括查询字段较多的PO类。PO类中应该包括上边SQL查询出来的所有字段,如下:
OrdersCustom.java |
package app.domain; //订单的扩展类 //通过此类映射订单和用户查询的结果,让此类继承包括字段较多的POJO类 public class OrdersCustomextends Orders{ //添加用户属性 /*USER.username, USER.sex, USER.address */ private String username; //用户名称 private String sex; // 用户性别 private String address; // 用户地址 // Getter & Setter } |
OrdersCustom类继承Orders类后OrdersCustom类包括了Orders类的所有字段,只需要定义用户的信息字段即可。
OrdersMapperCustom.xml |
<!-- 查询订单关联查询用户信息--> <select id="findOrdersUser" resultType="app.domain.OrdersCustom"> SELECT orders.*, USER.username,USER.sex,USER.address FROM orders, USER WHERE orders.user_id = user.id </select> |
OrdersMapperCustom.java |
package app.mapper; import java.util.List; import app.domain.Orders; import app.domain.OrdersCustom; import app.domain.User; //订单mapper public interface OrdersMapperCustom { //查询订单关联查询用户信息 public List<OrdersCustom>findOrdersUser()throws Exception; } |
OrdersMapperCustomTest.java |
@Test public void testFindOrdersUser()throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); OrdersMapperCustom ordersMapperCustom =sqlSession .getMapper(OrdersMapperCustom.class); //创建代理对象 List<OrdersCustom> list = ordersMapperCustom.findOrdersUser();// 调用maper的方法 System.out.println(list); sqlSession.close(); } |
定义专门的po类作为输出类型,其中定义了sql查询结果集所有的字段。此方法较为简单,企业中使用普遍。
使用resultMap,定义专门的resultMap用于映射一对一查询结果。
使用resultMap将查询结果中的订单信息映射到Orders对象中,在Orders类中添加User属性,将关联查询出来的用户信息映射到orders对象中的user属性中。
SELECT orders.*, user.username, user.address FROM orders, user WHERE orders.user_id = user.id
在Orders类中加入User属性,user属性中用于存储关联查询的用户信息,因为订单关联查询用户是一对一关系,所以这里使用单个User对象存储关联查询的用户信息。
Orders.java |
package app.domain; import java.util.Date; import java.util.List; public class Orders { private Integer id; private Integer userId; private String number; private Date createtime; private String note; private Useruser; //用户信息 private List<Orderdetail>orderdetails; //订单明细 //Getter & Setter } |
需要关联查询映射的是用户信息,使用association将用户信息映射到订单对象的用户属性中。
OrdersMapperCustom.xml |
<!-- 订单查询关联用户的resultMap 将整个查询的结果映射到app.domain.Orders中 --> <resultMap type="app.domain.Orders"id="OrdersUserResultMap"> <!-- 配置映射的订单信息--> <!--这里的id,是MyBatis在进行一对一查询时将user字段映射为user对象时要使用,必须写。 id:指定查询列中的唯一标识,订单信息的中的唯一标识,如果有多个列组成唯一标识,配置多个id。 column:订单信息的唯 一标识列 property:订单信息的唯 一标识 列所映射到Orders中哪个属性 --> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> <!-- 配置映射的关联的用户信息--> <!-- association:用于映射关联查询单个对象的信息 property:要将关联查询的用户信息映射到Orders中哪个属性。 --> <association property="user" javaType="app.domain.User"> <!--这里的id为user的id,如果写上表示给user的id属性赋值。 id:关联查询用户的唯一标识 column:指定唯 一标识用户信息的列 property:映射到user的哪个属性 --> <id column="user_id" property="id"/> <result column="username" property="username"/> <result column="sex" property="sex"/> <result column="address" property="address"/> </association> </resultMap> |
association:表示进行关联查询单条记录
property:表示关联查询的结果存储在app.domain.Orders的user属性中
javaType:表示关联查询的结果类型
<id property="id" column="user_id"/>:查询结果的user_id列对应关联对象的id属性,这里是<id />表示user_id是关联查询对象的唯一标识。
<result property="username" column="username"/>:查询结果的username列对应关联对象的username属性。
OrdersMapperCustom.xml |
<!-- 查询订单关联查询用户信息,使用resultmap --> <select id="findOrdersUserResultMap" resultMap="OrdersUserResultMap"> SELECT orders.*, USER.username, USER.sex, USER.address FROM orders, USER WHERE orders.user_id = user.id </select> |
这里resultMap指定OrdersUserResultMap。
OrdersMapperCustom.java |
package app.mapper; import java.util.List; import app.domain.Orders; import app.domain.OrdersCustom; import app.domain.User; //订单mapper public interface OrdersMapperCustom { //查询订单关联查询用户信息 public List<OrdersCustom> findOrdersUser()throws Exception; //查询订单关联查询用户使用resultMap public List<Orders> findOrdersUserResultMap()throws Exception; //查询订单(关联用户)及订单明细 public List<Orders> findOrdersAndOrderDetailResultMap()throws Exception; //查询用户购买商品信息 public List<User> findUserAndItemsResultMap()throws Exception; //查询订单关联查询用户,用户信息是延迟加载 public List<Orders> findOrdersUserLazyLoading()throws Exception; } |
OrdersMapperCustomTest.java |
@Test public void testFindOrdersUserResultMap()throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建代理对象 OrdersMapperCustom ordersMapperCustom =sqlSession .getMapper(OrdersMapperCustom.class); // 调用maper的方法 List<Orders> list = ordersMapperCustom.findOrdersUserResultMap(); System.out.println(list); sqlSession.close(); } |
使用association完成关联查询,将关联查询信息映射到POJO对象中。
实现一对一查询:
resultType:使用resultType实现较为简单,如果POJO中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。如果没有查询结果的特殊要求建议使用resultType。
resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射POJO的属性中。
resultMap可以实现延迟加载,resultType无法实现延迟加载。
查询所有订单信息及订单下的订单明细信息。订单信息与订单明细为一对多关系。
使用resultMap实现如下:
确定主查询表:订单表
确定关联查询表:订单明细表
在一对一查询基础上添加订单明细表关联即可。
orders.*,user.username,user.address,orderdetail.id orderdetail_id,orderdetail.items_id,orderdetail.items_num
FROM orders,user,orderdetail
WHERE orders.user_id = user.id AND orders.id = orderdetail.orders_id
使用resultType将上边的查询结果映射到pojo中,订单信息的就是重复。
要求:对orders映射不能出现重复记录。
在orders.java类中添加List<orderDetail> orderDetails属性。
最终会将订单信息映射到orders中,订单所对应的订单明细映射到orders中的orderDetails属性中。
映射成的orders记录数为两条(orders信息不重复)
每个orders中的orderDetails属性存储了该订单所对应的订单明细。
在Orders类中加入User属性。在Orders类中加入List<Orderdetail> orderdetails属性(参考一对一)。
Orders.java |
private User user; //用户信息 private List<Orderdetail>orderdetails; //订单明细 |
resultMap使用继承:resultMap中的一部分和一对一查询订单信息的resultMap相同,这里使用继承可以不再填写重复的内容,如下:
OrdersMapperCustom.xml |
<!-- 订单及订单明细的resultMap 使用extends继承,不用在中配置订单信息和用户信息的映射 --> <resultMap type="app.domain.Orders" id="OrdersAndOrderDetailResultMap" extends="OrdersUserResultMap"> <!-- 订单信息 --> <!-- 用户信息 --> <!-- 使用extends继承,不用在中配置订单信息和用户信息的映射--> <!-- 订单明细信息 一个订单关联查询出了多条明细,要使用collection进行映射 collection:对关联查询到多条记录映射到集合对象中 property:将关联查询到多条记录映射到app.domain.Orders哪个属性 ofType:指定映射到list集合属性中pojo的类型 --> <collection property="orderdetails" ofType="app.domain.Orderdetail"> <!-- id:订单明细唯一标识 property:要将订单明细的唯一标识映射到app.domain.Orderdetail的哪个属性 --> <id column="orderdetail_id" property="id"/> <result column="items_id" property="itemsId"/> <result column="items_num" property="itemsNum"/> <result column="orders_id" property="ordersId"/> </collection> </resultMap> |
collection部分定义了查询订单明细信息。
collection:表示关联查询结果集
property="orderdetails":关联查询的结果集存储在app.domain.Orders上哪个属性。
ofType="app.domain.Orderdetail":指定关联查询的结果集中的对象类型即List中的对象类型。
<id />及<result/>的意义同一对一查询。
使用extends继承订单信息OrdersUserResultMap。
OrdersMapperCustom.xml |
<!-- 查询订单关联查询用户及订单明细,使用resultmap --> <select id="findOrdersAndOrderDetailResultMap" resultMap="OrdersAndOrderDetailResultMap"> SELECT orders.*, USER.username, USER.sex, USER.address, orderdetail.id orderdetail_id, orderdetail.items_id, orderdetail.items_num, orderdetail.orders_id FROM orders, USER, orderdetail WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id </select> |
//查询订单(关联用户)及订单明细 public List<Orders> findOrdersAndOrderDetailResultMap()throws Exception; |
@Test public void testFindOrdersAndOrderDetailResultMap()throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建代理对象 OrdersMapperCustom ordersMapperCustom =sqlSession .getMapper(OrdersMapperCustom.class); // 调用maper的方法 List<Orders> list = ordersMapperCustom.findOrdersAndOrderDetailResultMap(); System.out.println(list); sqlSession.close(); } |
MyBatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中。
使用resultType实现:将订单明细映射到orders中的orderdetails中,需要自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在orderdetails中。比较麻烦。
查询用户购买的商品信息。
需要查询所有用户信息,关联查询订单及订单明细信息,订单明细信息中关联查询商品信息。
查询主表是:用户表
关联表:由于用户和商品没有直接关联,通过订单和订单明细进行关联,所以关联表:orders、orderdetail、items。
SELECT orders.*, USER.username, USER.sex, USER.address, orderdetail.id orderdetail_id, orderdetail.items_id, orderdetail.items_num, orderdetail.orders_id, items.name items_name, items.detail items_detail, items.price items_price FROM orders, USER, orderdetail, items WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id AND orderdetail.items_id = items.id |
将用户信息映射到user中。
在user类中添加订单列表属性List<Orders> orderslist,将用户创建的订单映射到orderslist。
在Orders中添加订单明细列表属性List<OrderDetail>orderdetials,将订单的明细映射到orderdetials。
在OrderDetail中添加Items属性,将订单明细所对应的商品映射到Items。
在User中添加List<Orders> orderslist属性,在Orders类中加入List<Orderdetail> orderdetails属性,在OrderDetail中添加Items属性。
需要关联查询映射的信息是:订单、订单明细、商品信息。
订单:一个用户对应多个订单,使用collection映射到用户对象的订单列表属性中。
订单明细:一个订单对应多个明细,使用collection映射到订单对象中的明细属性中。
商品信息:一个订单明细对应一个商品,使用association映射到订单明细对象的商品属性中。
OrdersMapperCustom.xml |
<!-- 查询用户及购买的商品--> <resultMap type="app.po.User" id="UserAndItemsResultMap"> <!-- 用户信息 --> <id column="user_id" property="id"/> <result column="username" property="username"/> <result column="sex" property="sex"/> <result column="address" property="address"/> <!-- 订单信息 一个用户对应多个订单,使用collection映射 --> <collection property="ordersList" ofType="app.po.Orders"> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> <!-- 订单明细 一个订单包括 多个明细 --> <collection property="orderdetails" ofType="app.po.Orderdetail"> <id column="orderdetail_id" property="id"/> <result column="items_id" property="itemsId"/> <result column="items_num" property="itemsNum"/> <result column="orders_id" property="ordersId"/> <!-- 商品信息 一个订单明细对应一个商品 --> <association property="items" javaType="app.po.Items"> <id column="items_id" property="id"/> <result column="items_name" property="name"/> <result column="items_detail" property="detail"/> <result column="items_price" property="price"/> </association> </collection> </collection> </resultMap> |
OrdersMapperCustom.xml |
<!-- 查询用户及购买的商品信息,使用resultmap --> <select id="findUserAndItemsResultMap" resultMap="UserAndItemsResultMap"> SELECT orders.*, USER.username, USER.sex, USER.address, orderdetail.id orderdetail_id, orderdetail.items_id, orderdetail.items_num, orderdetail.orders_id, items.name items_name, items.detail items_detail, items.price items_price FROM orders, USER, orderdetail, items WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id AND orderdetail.items_id = items.id </select> |
//查询用户购买商品信息 public List<User> findUserAndItemsResultMap()throws Exception; |
一对多是多对多的特例,如下需求:
查询用户购买的商品信息,用户和商品的关系是多对多关系。
需求1:
查询字段:用户账号、用户名称、用户性别、商品名称、商品价格(最常见)。企业开发中常见明细列表,用户购买商品明细列表,使用resultType将上边查询列映射到POJO输出。
需求2:
查询字段:用户账号、用户名称、购买商品数量、商品明细(鼠标移上显示明细)。使用resultMap将用户购买的商品明细列表映射到user对象中。
总结:使用resultMap是针对那些对查询结果映射有特殊要求的功能,比如特殊要求映射成list中包括多个list。
1、resultType:
作用:将查询结果按照sql列名POJO属性名一致性映射到POJO中。
场合:常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到POJO中,在前端页面遍历list(list中是POJO)即可。
2、resultMap:
使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。
association:
作用:将关联查询信息映射到一个POJO对象中。
场合:为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的POJO属性中,比如:查询订单及关联用户信息。
使用resultType无法将查询结果映射到POJO对象的POJO属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。
collection:
作用:将关联查询信息映射到一个list集合中。
场合:为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的作的目的也是方便对查询结果集进行遍历查询。
如果使用resultType无法将查询结果映射到list集合中。
需要查询关联信息时,使用MyBatis延迟加载特性可有效的减少数据库压力,首次查询只查询主要信息,关联信息等用户获取时再加载。
resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。
需求:如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。
延迟加载:先从单表查询、需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
MyBatis默认没有开启延迟加载,需要在SqlMapConfig.xml中setting配置。在MyBatis核心配置文件中配置:lazyLoadingEnabled、aggressiveLazyLoading
设置项 |
描述 |
允许值 |
默认值 |
lazyLoadingEnabled |
全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。 |
true | false |
false |
aggressiveLazyLoading |
当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 |
true | false |
true |
SqlMapConfig.xml |
<settings> <!-- 打开延迟加载 的开关--> <setting name="lazyLoadingEnabled" value="true"/> <!-- 将积极加载改为消极加载即按需要加载--> <setting name="aggressiveLazyLoading" value="false"/> </settings> |
查询订单信息,关联查询用户信息。默认只查询订单信息,当需要查询用户信息时再去查询用户信息。
SELECT orders.* FROM orders
在Orders类中加入User属性。
public class Orders { //... private User user; //用户信息 |
需要定义两个mapper的方法对应的statement。
1、只查询订单信息
SELECT * FROM orders
在查询订单的statement中使用association去延迟加载(执行)下边的satatement(关联查询用户信息)
OrdersMapperCustom.xml |
<!-- 查询订单关联查询用户,用户信息需要延迟加载--> <select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoadingResultMap"> SELECT * FROM orders </select> |
2、关联查询用户信息
通过上边查询到的订单信息中user_id去关联查询用户信息
使用UserMapper.xml中的findUserById
<select id="findUserById" parameterType="int" resultType="user"> SELECT * FROM USER WHERE id=#{value} </select> |
上边先去执行findOrdersUserLazyLoading,当需要去查询用户的时候再去执行findUserById,通过resultMap的定义将延迟加载执行配置起来。
使用association中的select指定延迟加载去执行的statement的id。
<!-- 延迟加载的resultMap --> <resultMap type="app.po.Orders" id="OrdersUserLazyLoadingResultMap"> <!--对订单信息进行映射配置 --> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> <!-- 实现对用户信息进行延迟加载 select:指定延迟加载需要执行的statement的id(是根据user_id查询用户信息的statement) 要使用userMapper.xml中findUserById完成根据用户id(user_id)用户信息的查询,如果findUserById不在本mapper中需要前边加namespace column:订单信息中关联用户信息查询的列,是user_id 关联查询的sql理解为: SELECT orders.*, (SELECT username FROM USER WHERE orders.user_id = user.id)username, (SELECT sex FROM USER WHERE orders.user_id = user.id)sex FROM orders --> <association property="user" javaType="app.po.User" select="app.mapper.UserMapper.findUserById" column="user_id"> <!-- 实现对用户信息进行延迟加载--> </association> </resultMap> |
association:
select="findUserById":指定关联查询sql为findUserById
column="user_id":关联查询时将users_id列的值传入findUserById
最后将关联查询结果映射至app.domain.User。
//查询订单关联查询用户,用户信息是延迟加载 public List<Orders> findOrdersUserLazyLoading()throws Exception; |
1、执行上边mapper方法(findOrdersUserLazyLoading),内部去调用app.mapper.OrdersMapperCustom中的findOrdersUserLazyLoading只查询orders信息(单表)。
2、在程序中去遍历上一步骤查询出的List<Orders>,当我们调用Orders中的getUser方法时,开始进行延迟加载。
3、延迟加载,去调用UserMapper.xml中findUserbyId这个方法获取用户信息。
MyBatis默认没有开启延迟加载,需要在SqlMapConfig.xml中setting配置。参考前面的配置文件。
OrdersMapperCustomTest.java |
// 查询订单关联查询用户,用户信息使用延迟加载 @Test public void testFindOrdersUserLazyLoading()throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession();// 创建代理对象 OrdersMapperCustom ordersMapperCustom =sqlSession .getMapper(OrdersMapperCustom.class); // 查询订单信息(单表) List<Orders> list = ordersMapperCustom.findOrdersUserLazyLoading(); // 遍历上边的订单列表 for (Orders orders : list) { // 执行getUser()去查询用户信息,这里实现按需加载 User user = orders.getUser(); System.out.println(user); } } |
不使用MyBatis提供的association及collection中的延迟加载功能,如何实现延迟加载?
实现方法如下:
针对订单和用户两个表定义两个mapper方法。
1、订单查询mapper方法
2、根据用户id查询用户信息mapper方法
实现思路:
先去查询第一个mapper方法,默认使用订单查询mapper方法只查询订单信息,获取订单信息列表。
在程序中(service),当需要关联查询用户信息时,再调用根据用户id查询用户信息的第二个mapper方法查询用户信息。
总之:
使用延迟加载方法,先去查询简单的sql(最好单表,也可以关联查询),再去按需要加载关联查询的其它信息。
一对多延迟加载的方法同一对一延迟加载,在collection标签中配置select内容。本部分内容自学。
作用:
当需要查询关联信息时再去数据库查询,默认不去关联查询,提高数据库性能。
只有使用resultMap支持延迟加载设置。
场合:
当只有部分记录需要关联查询其它信息时,此时可按需延迟加载,需要关联查询时再向数据库发出sql,以提高数据库性能。
当全部需要关联查询信息时,此时不用延迟加载,直接将关联查询信息全部返回即可,可使用resultType或resultMap完成映射。
查询得到男性或女性的数量,如果传入的是0就女性否则是男性。
create table p_user( id int primary key auto_increment, name varchar(10), sex char(2) ); insert into p_user(name,sex)values(‘A‘,"男"); insert into p_user(name,sex)values(‘B‘,"女"); insert into p_user(name,sex)values(‘C‘,"男"); ------- 创建存储过程(查询得到男性或女性的数量, 如果传入的是0就女性否则是男性) ------ DELIMITER $ CREATE PROCEDURE mybatis.ges_user_count(IN sex_idINT, OUT user_count INT) BEGIN IF sex_id=0 THEN SELECT COUNT(*)FROM mybatis.p_user WHERE p_user.sex=‘女‘ INTO user_count; ELSE SELECT COUNT(*)FROM mybatis.p_user WHERE p_user.sex=‘男‘ INTO user_count; END IF; END $ ---------------- 调用存储过程 ------------------------------------------------------ DELIMITER ; SET @user_count = 0; CALL mybatis.ges_user_count(1,@user_count); SELECT @user_count; |
编辑userMapper.xml文件,添加如下的配置项。
<!-- 查询得到男性或女性的数量, 如果传入的是0就女性否则是男性 --> <selectid="getUserCount" parameterMap="getUserCountMap" statementType="CALLABLE"> CALL mybatis.ges_user_count(?,?) </select> <!-- parameterMap.put("sexid", 0); parameterMap.put("usercount", -1); --> <parameterMaptype="java.util.Map" id="getUserCountMap"> <parameterproperty="sexid" mode="IN" jdbcType="INTEGER"/> <parameterproperty="usercount" mode="OUT" jdbcType="INTEGER"/> </parameterMap> |
package app.test; import java.util.HashMap; import java.util.List; import java.util.Map; import app.custom.model.ConditionUser; import app.domain.User; import app.util.MyBatisUtil; import org.apache.ibatis.session.SqlSession; import org.junit.Test; public class Test6 { @Test public void testGetUserCount(){ SqlSession sqlSession = MyBatisUtil.getSqlSession(); String statement = "app.mapping.userMapper.getUserCount";//映射sql的标识字符串 Map<String, Integer> parameterMap =new HashMap<String, Integer>(); parameterMap.put("sexid", 1); parameterMap.put("usercount", -1); sqlSession.selectOne(statement, parameterMap); Integer result = parameterMap.get("usercount"); System.out.println(result); sqlSession.close(); } } |
正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持。
1. 一级缓存: 基于PerpetualCache 的HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有Cache 就将清空。
2. 二级缓存与一级缓存其机制相同,默认也是采用PerpetualCache,HashMap存储,不同在于其存储作用域为Mapper(Namespace),并且可自定义存储源,如Ehcache。
3. 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有select 中的缓存将被clear。
如下图,是MyBatis一级缓存和二级缓存的区别图解:
MyBatis一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的SQL语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
MyBatis二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的SQL语句且向SQL中传递参数也相同即最终执行相同的SQL语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。MyBatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存。
下图是根据id查询用户的一级缓存图解:
一级缓存区域是根据SqlSession为单位划分的。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
MyBatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象
sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。
//获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //第一次查询 User user1 = userMapper.findUserById(1); System.out.println(user1); //第二次查询,由于是同一个session则不再向数据发出语句直接从缓存取出 User user2 = userMapper.findUserById(1); System.out.println(user2); //关闭session session.close(); |
//获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //第一次查询 User user1 = userMapper.findUserById(1); System.out.println(user1); //在同一个session执行更新 User user_update = new User(); user_update.setId(1); user_update.setUsername("李奎"); userMapper.updateUser(user_update); session.commit(); //第二次查询,虽然是同一个session但是由于执行了更新操作session的缓存被清空,这里重新发出sql操作 User user2 = userMapper.findUserById(1); System.out.println(user2); |
正式开发,是将MyBatis和Spring进行整合开发,事务控制在service中。一个service方法中包括很多mapper方法调用。
service{
//开始执行时,开启事务,创建SqlSession对象
//第一次调用mapper的方法findUserById(1)
//第二次调用mapper的方法findUserById(1),从一级缓存中取数据
//方法结束,sqlSession关闭
}
如果是执行两次service调用查询相同的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。
下图是多个sqlSession请求UserMapper的二级缓存图解。
二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域,如果使用mapper代理方法每个mapper的namespace都不同,此时可以理解为二级缓存区域是根据mapper划分。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
MyBatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象。sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。
1、首先开启mybatis的二级缓存。
2、sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。
3、sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
4、如果SqlSession3去执行相同mapper下sql,执行commit提交,清空该mapper下的二级缓存区域的数据。
二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。UserMapper有一个二级缓存区域(按namespace分),其它mapper也有自己的二级缓存区域(按namespace分)。每一个namespace的mapper都有一个二缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。
1、在核心配置文件SqlMapConfig.xml中加入
<setting name="cacheEnabled" value="true"/>
|
描述 |
允许值 |
默认值 |
cacheEnabled |
对在此配置文件下的所有cache 进行全局性开/关设置。 |
true false |
true |
2、要在你的Mapper映射文件中添加一行: <cache /> ,表示此mapper开启二级缓存。
二级缓存需要查询结果映射的POJO对象实现java.io.Serializable接口实现序列化和反序列化操作,注意如果存在父类、成员POJO都需要实现序列化接口。
public class Orders implements Serializable
public class User implements Serializable
....
//获取session1 SqlSession session1 = sqlSessionFactory.openSession(); UserMapper userMapper = session1.getMapper(UserMapper.class); //使用session1执行第一次查询 User user1 = userMapper.findUserById(1); System.out.println(user1); //关闭session1 session1.close(); //获取session2 SqlSession session2 = sqlSessionFactory.openSession(); UserMapper userMapper2 = session2.getMapper(UserMapper.class); //使用session2执行第二次查询,由于开启了二级缓存这里从缓存中获取数据不再向数据库发出sql User user2 = userMapper2.findUserById(1); System.out.println(user2); //关闭session2 session2.close(); |
在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
总结:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。
在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。
设置statement配置中的flushCache="true" 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
如下:<insert id="insertUser" parameterType="app.domain.User" flushCache="true">
总结:一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。
cache标签常用属性:
<cache eviction="FIFO" <!--回收策略为先进先出--> flushInterval="60000" <!--自动刷新时间60s--> size="512" <!--最多缓存512个引用对象--> readOnly="true"/> <!--只读--> |
flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
readOnly(只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
如下例子:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。可用的收回策略有(默认的是 LRU):
1. LRU – 最近最少使用的:移除最长时间不被使用的对象。
2. FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
3. SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
4. WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
1. 映射语句文件中的所有select语句将会被缓存。
2. 映射语句文件中的所有insert,update和delete语句会刷新缓存。
3. 缓存会使用Least Recently Used(LRU,最近最少使用的)算法来收回。
4. 缓存会根据指定的时间间隔来刷新。
5. 缓存会存储1024个对象
EhCache 是一个纯Java的进程内缓存框架,是一种广泛使用的开源Java分布式缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
我们系统为了提高系统并发,性能、一般对系统进行分布式部署(集群部署方式)
不使用分布缓存,缓存的数据在各各服务单独存储,不方便系统 开发。所以要使用分布式缓存对缓存数据进行集中管理。
MyBatis无法实现分布式缓存,需要和其它分布式缓存框架进行整合。
MyBatis提供二级缓存Cache接口,如下:
它的默认实现类:
通过实现Cache接口可以实现MyBatis缓存数据通过其它缓存数据库整合,MyBatis的特长是sql操作,缓存数据的管理不是MyBatis的特长,为了提高缓存的性能将MyBatis和第三方的缓存数据库整合,比如ehcache、memcache、redis等。
maven坐标:
<dependency> <groupId>org.MyBatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.0.2</version> </dependency> |
classpath下添加ehcache.xml。内容如下:
ehcache.xml |
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <diskStore path="F:\develop\ehcache" /> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache> |
属性说明:
l diskStore:指定数据在磁盘中的存储位置。
l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略
以下属性是必须的:
l maxElementsInMemory - 在内存中缓存的element的最大数目
l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
以下属性是可选的:
l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
l diskPersistent- 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
EhcacheCache是ehcache对Cache接口的实现:
修改mapper.xml文件,在cache中指定EhcacheCache。
XxxMapper.xml |
<mapper namespace="app.mapper.UserMapper"> <!-- 开启本mapper的namespace下的二缓存 type:指定cache接口的实现类的类型,mybatis默认使用PerpetualCache 要和ehcache整合,需要配置type为ehcache实现cache接口的类型 --> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/> ... |
根据需求调整缓存参数:
<cache type="org.mybatis.caches.ehcache.EhcacheCache" > <property name="timeToIdleSeconds" value="3600"/> <property name="timeToLiveSeconds" value="3600"/> <!-- 同ehcache参数maxElementsInMemory --> <property name="maxEntriesLocalHeap" value="1000"/> <!-- 同ehcache参数maxElementsOnDisk --> <property name="maxEntriesLocalDisk" value="10000000"/> <property name="memoryStoreEvictionPolicy" value="LRU"/> </cache> |
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用MyBatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。
实现方法如下:通过设置刷新间隔时间,由MyBatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。
MyBatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用MyBatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为MyBatis的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。
实现MyBatis与spring进行整合,通过spring管理SqlSessionFactory、mapper接口。
1、需要Spring通过单例方式管理SqlSessionFactory。
2、Spring和MyBatis整合生成代理对象,使用SqlSessionFactory创建SqlSession。(Spring和MyBatis整合自动完成)。
3、持久层的mapper都需要由Spring进行管理。
1、创建一个新的java工程(接近实际开发的工程结构)
2、加入相关jar包
mybatis3.2.7的jar包
spring4.2.0的jar包
MyBatis官方提供与MyBatis与spring整合jar包:早期ibatis和spring整合是由spring官方提供,mybatis和spring整合由mybatis提供。
数据库连接池,如DBCP等
数据库驱动
在classpath下创建mybatis/SqlMapConfig.xml
SqlMapConfig.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> <!-- 批量别名定义 指定包名,mybatis自动扫描包中的po类,自动定义别名,别名就是类名(首字母大写或小写都可以) --> <package name="cn.itcast.ssm.po"/> </typeAliases> <!-- 加载 映射文件 --> <mappers> <mapper resource="sqlmap/User.xml"/> <!-- 批量加载mapper 指定mapper接口的包名,mybatis自动扫描包下边所有mapper接口进行加载 遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录 中 上边规范的前提是:使用的是mapper代理方法 和spring整合后,使用mapper扫描器,这里不需要配置了 --> <!-- <package name="cn.itcast.ssm.mapper"/> --> </mappers> </configuration> |
在classpath下创建applicationContext.xml,定义数据库链接池、SqlSessionFactory。sqlSessionFactory在mybatis和spring的整合包下。
applicationContext.xml |
<!-- 加载配置文件 --> <context:property-placeholder location="classpath:db.properties" /> <!-- 数据源,使用dbcp --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxActive" value="10" /> <property name="maxIdle" value="5" /> </bean> <!-- mapper配置 --> <!-- 让spring管理sqlsessionfactory使用mybatis和spring整合包中的--> <!-- sqlSessinFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 加载mybatis的全局配置文件--> <property name="configLocation" value="mybatis/SqlMapConfig.xml" /> <!-- 数据源 --> <property name="dataSource" ref="dataSource" /> </bean> |
注意:在定义sqlSessionFactory时指定数据源dataSource和mybatis的配置文件。
使用此种方法即原始DAO开发方法,需要编写DAO接口,DAO接口实现类、映射文件。
1、 User.xml:参考入门程序。
2、 在sqlMapConfig.xml中配置映射文件的位置:参考入门程序。
<mappers> <mapper resource="mapper.xml文件的地址" /> <mapper resource="mapper.xml文件的地址" /> </mappers> |
3、 定义dao接口:参考入门程序。
4、 dao接口实现类集成SqlSessionDaoSupport
dao接口实现类需要注入SqlSessoinFactory,通过spring进行注入。这里spring声明配置方式,配置dao的bean:
让UserDaoImpl实现类继承SqlSessionDaoSupport。
dao接口实现类方法中可以this.getSqlSession()进行数据增删改查。
UserDaoImpl.java |
package app.dao; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.support.SqlSessionDaoSupport; import cn.itcast.ssm.po.User; //dao接口实现类 public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao { @Override public User findUserById(int id)throws Exception { //继承SqlSessionDaoSupport,通过this.getSqlSession()得到sqlSessoin SqlSession sqlSession = this.getSqlSession(); User user = sqlSession.selectOne("test.findUserById",id); return user; } } |
5、 Spring 配置dao
在applicationContext.xml中配置dao。
applicationContext.xml |
<!-- 原始dao接口--> <bean id="userDao" class="app.dao.UserDaoImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> |
6、 测试程序
UserDaoImplTest.java |
package cn.itcast.ssm.dao; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.itcast.ssm.po.User; public class UserDaoImplTest { private ApplicationContextapplicationContext; //在setUp这个方法得到spring容器 @Before public void setUp()throws Exception { applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml"); } @Test public void testFindUserById()throws Exception { UserDao userDao = (UserDao) applicationContext.getBean("userDao"); //调用userDao的方法 User user = userDao.findUserById(1); System.out.println(user); } } |
此方法即mapper接口开发方法,只需定义mapper接口,不用编写mapper接口实现类。每个mapper接口都需要在spring配置文件中定义。
1、 在sqlMapConfig.xml中配置mapper.xml的位置
如果mapper.xml和mappre接口的名称相同且在同一个目录,这里可以不用配置。
<mappers> <mapper resource="mapper.xml文件的地址" /> <mapper resource="mapper.xml文件的地址" /> </mappers> |
2、 定义mapper接口
3、 Spring中定义
<bean id="" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="mapper接口地址"/> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> |
例如:
<!-- mapper配置 MapperFactoryBean:根据mapper接口生成代理对象 --> <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <!-- mapperInterface指定mapper接口 --> <property name="mapperInterface" value="cn.itcast.ssm.mapper.UserMapper"/> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> |
此方法问题:需要针对每个mapper进行配置,麻烦。
此方法即mapper接口开发方法,只需定义mapper接口,不用编写mapper接口实现类。只需要在spring配置文件中定义一个mapper扫描器,自动扫描包中的mapper接口生成代代理对象。
1、 mapper.xml文件编写
2、 定义mapper接口
注意mapper.xml的文件名和mapper的接口名称保持一致,且放在同一个目录
3、 配置mapper扫描器
<!-- mapper批量扫描,从mapper包中扫描出mapper接口,自动创建代理对象并且在spring容器中注册。 遵循规范:将mapper.java和mapper.xml映射文件名称保持一致,且在一个目录中 自动扫描出来的mapper的bean的id为mapper类名(首字母小写) --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 指定扫描的包名:如果扫描多个包,每个包中间使用半角逗号分隔。 --> <property name="basePackage" value="app.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> |
basePackage:扫描包路径,中间可以用逗号或分号分隔定义多个包
4、 使用扫描器后从Spring容器中获取mapper的实现对象
如果将mapper.xml和mapper接口的名称保持一致且放在一个目录,则不用在sqlMapConfig.xml中进行配置。
MyBatis需要程序员自己编写SQL语句,MyBatis官方提供逆向工程(使用官方网站的mapper自动生成工具mybatis-generator-core-1.3.2来生成PO类和mapper映射文件)可以针对单表自动生成MyBatis执行所需要的代码(mapper接口mapper.java、mapper.xml、po..)。
企业实际开发中,常用的逆向工程方式:由于数据库的表生成java代码。
http://mybatis.github.io/generator/
https://github.com/mybatis/generator
解压下载的zip文件,打开docs下的index.html查看用户指南。
建议使用Java程序方式,不依赖开发工具。
在generatorConfig.xml中配置mapper生成的详细信息,注意改下几点:
1、 添加要生成的数据库表;
2、 PO文件所在包路径;
3、 mapper文件所在包路径。
配置文件如下:
generatorConfig.xml |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <context id="testTables" targetRuntime="MyBatis3"> <commentGenerator> <!-- 是否去除自动生成的注释true:是 :false:否--> <property name="suppressAllComments" value="true" /> </commentGenerator> <!--数据库连接的信息:驱动类、连接地址、用户名、密码--> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" password="root"> </jdbcConnection> <!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver" connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg" userId="yycg" password="yycg"> </jdbcConnection> --> <!-- 默认false,把JDBC DECIMAL和NUMERIC类型解析为Integer,为true时把JDBC DECIMAL和 NUMERIC类型解析为java.math.BigDecimal --> <javaTypeResolver> <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!—targetProject:生成PO类的位置--> <javaModelGenerator targetPackage="app.domain"targetProject=".\src"> <!-- enableSubPackages:是否让schema作为包的后缀--> <property name="enableSubPackages" value="false" /> <!-- 从数据库返回的值被清理前后的空格--> <property name="trimStrings" value="true" /> </javaModelGenerator> <!-- targetProject:mapper映射文件生成的位置--> <sqlMapGenerator targetPackage="app.mapper" targetProject=".\src"> <!-- enableSubPackages:是否让schema作为包的后缀--> <property name="enableSubPackages" value="false" /> </sqlMapGenerator> <!-- targetPackage:mapper接口生成的位置--> <javaClientGenerator type="XMLMAPPER" targetPackage="app.mapper" targetProject=".\src"> <!-- enableSubPackages:是否让schema作为包的后缀--> <property name="enableSubPackages" value="false" /> </javaClientGenerator> <!-- 指定数据库表--> <table tableName="items"></table> <table tableName="orders"></table> <table tableName="orderdetail"></table> <table tableName="user"></table> <!-- <table schema="" tableName="sys_user"></table> <table schema="" tableName="sys_role"></table> <table schema="" tableName="sys_permission"></table> <table schema="" tableName="sys_user_role"></table> <table schema="" tableName="sys_role_permission"></table> --> <!-- 有些表的字段需要指定java类型 <table schema="" tableName=""> <columnOverride column="" javaType="" /> </table> --> </context> </generatorConfiguration> |
GeneratorSqlmap.java |
import java.io.File; import java.util.ArrayList; import java.util.List; import org.mybatis.generator.api.MyBatisGenerator; import org.mybatis.generator.config.Configuration; import org.mybatis.generator.config.xml.ConfigurationParser; import org.mybatis.generator.internal.DefaultShellCallback; public class GeneratorSqlmap { public void generator()throws Exception{ List<String> warnings = new ArrayList<String>(); boolean overwrite =true; //指定逆向工程配置文件 File configFile = new File("generatorConfig.xml"); ConfigurationParser cp = new ConfigurationParser(warnings); Configuration config = cp.parseConfiguration(configFile); DefaultShellCallback callback = new DefaultShellCallback(overwrite); MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings); myBatisGenerator.generate(null); } public static void main(String[]args) throws Exception { try { GeneratorSqlmap generatorSqlmap = new GeneratorSqlmap(); generatorSqlmap.generator(); } catch (Exception e) { e.printStackTrace(); } } } |
生成后的代码:见上图。
需要将生成工程中所生成的代码拷贝到自己的工程中。
将PO文件拷贝至PO类所在的目录内。注意:最好不要修改PO类文件,因为可能会修改字段,会导致PO类改变,覆盖PO类文件会使用修改信息丢失,故最好继承PO类。
User.java |
package app.domain; import java.util.Date; public class User { private Integer id; private String username; private Date birthday; private String sex; private String address; //Getter & Setter } |
学会使用mapper的条件查询:
XxxExample.java |
package app.domain; import java.util.ArrayList;import java.util.Date; import java.util.Iterator;import java.util.List; public class UserExample{ protected StringorderByClause; protected boolean distinct; protected List<Criteria>oredCriteria; public UserExample()// public void setOrderByClause(StringorderByClause) // public String getOrderByClause()// public void setDistinct(boolean distinct) // public boolean isDistinct()// public List<Criteria>getOredCriteria()// public void or(Criteriacriteria) // public Criteria or()// public Criteria createCriteria()// protected Criteria createCriteriaInternal()// public void clear()// // 生成查询条件 protected abstract static class GeneratedCriteria// protected GeneratedCriteria()// public boolean isValid()// public List<Criterion> getAllCriteria()// public List<Criterion> getCriteria()// protected void addCriterion(Stringcondition) // protected void addCriterion(Stringcondition, Object value, String property) // protected void addCriterion(Stringcondition, Object value1, Object value2, String property) // protected void addCriterionForJDBCDate(Stringcondition, Date value, String property) // protected void addCriterionForJDBCDate(Stringcondition, List<Date> values, String property) // protected void addCriterionForJDBCDate(Stringcondition, Date value1, Date value2, String property) //
public Criteria andIdIsNull()// //... public CriteriaandNameIsNull()// public CriteriaandNameIsNotNull()// public CriteriaandNameEqualTo(String value) // public CriteriaandNameNotEqualTo(String value) // public CriteriaandNameGreaterThan(String value) // public CriteriaandNameGreaterThanOrEqualTo(String value) // public CriteriaandNameLessThan(String value) // public CriteriaandNameLessThanOrEqualTo(String value) // public CriteriaandNameLike(String value) // public CriteriaandNameNotLike(String value) // public CriteriaandNameIn(List<String> values) // public CriteriaandNameNotIn(List<String> values) // public CriteriaandNameBetween(String value1, Stringvalue2) // public CriteriaandNameNotBetween(String value1, Stringvalue2) // // ... } public static class Criteriaextends GeneratedCriteria { protected Criteria() } // 查询条件类 public static class Criterion { private Stringcondition; private Objectvalue; private ObjectsecondValue; private boolean noValue; private boolean singleValue; private boolean betweenValue; private boolean listValue; private StringtypeHandler; //getter protected Criterion(Stringcondition) // protected Criterion(Stringcondition, Object value, String typeHandler) // protected Criterion(Stringcondition, Object value) // protected Criterion(Stringcondition, Object value, Object secondValue, StringtypeHandler) // protected Criterion(Stringcondition, Object value, Object secondValue) // } } |
将XxxMapper.xml的文件拷贝至mapper目录内。
UserMapper.xml |
<?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="app.mapper.UserMapper" > <resultMap id="BaseResultMap" type="app.domain.User" > <id column="id" property="id" jdbcType="INTEGER" /> <result column="username" property="username" jdbcType="VARCHAR" /> <result column="birthday" property="birthday" jdbcType="DATE" /> <result column="sex" property="sex" jdbcType="CHAR" /> <result column="address" property="address" jdbcType="VARCHAR" /> </resultMap> <sql id="Example_Where_Clause" > <where > <foreach collection="oredCriteria" item="criteria" separator="or" > <if test="criteria.valid" > <trim prefix="(" suffix=")" prefixOverrides="and" > <foreach collection="criteria.criteria" item="criterion" > <choose > <when test="criterion.noValue" > and ${criterion.condition} </when> <when test="criterion.singleValue" > and ${criterion.condition} #{criterion.value} </when> <when test="criterion.betweenValue" > and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} </when> <when test="criterion.listValue" > and ${criterion.condition} <foreach collection="criterion.value" item="listItem" open="(" close=")" separator="," > #{listItem} </foreach> </when> </choose> </foreach> </trim> </if> </foreach> </where> </sql> <sql id="Update_By_Example_Where_Clause" > <where > <foreach collection="example.oredCriteria" item="criteria" separator="or" > <if test="criteria.valid" > <trim prefix="(" suffix=")" prefixOverrides="and" > <foreach collection="criteria.criteria" item="criterion" > <choose > <when test="criterion.noValue" > and ${criterion.condition} </when> <when test="criterion.singleValue" > and ${criterion.condition} #{criterion.value} </when> <when test="criterion.betweenValue" > and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} </when> <when test="criterion.listValue" > and ${criterion.condition} <foreach collection="criterion.value" item="listItem" open="(" close=")" separator="," > #{listItem} </foreach> </when> </choose> </foreach> </trim> </if> </foreach> </where> </sql> <sql id="Base_Column_List" > id, username, birthday, sex, address </sql> <select id="selectByExample" resultMap="BaseResultMap" parameterType="app.domain.UserExample" > select <if test="distinct" > distinct </if> <include refid="Base_Column_List" /> from user <if test="_parameter != null" > <include refid="Example_Where_Clause" /> </if> <if test="orderByClause != null" > order by ${orderByClause} </if> </select> <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" > select <include refid="Base_Column_List" /> from user where id = #{id,jdbcType=INTEGER} </select> <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" > delete from user where id = #{id,jdbcType=INTEGER} </delete> <delete id="deleteByExample" parameterType="app.domain.UserExample" > delete from user <if test="_parameter != null" > <include refid="Example_Where_Clause" /> </if> </delete> <insert id="insert" parameterType="app.domain.User" > insert into user (id, username, birthday, sex, address) values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, #{birthday,jdbcType=DATE}, #{sex,jdbcType=CHAR}, #{address,jdbcType=VARCHAR}) </insert> <insert id="insertSelective" parameterType="app.domain.User" > insert into user <trim prefix="(" suffix=")" suffixOverrides="," > <if test="id != null" > id, </if> <if test="username != null" > username, </if> <if test="birthday != null" > birthday, </if> <if test="sex != null" > sex, </if> <if test="address != null" > address, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides="," > <if test="id != null" > #{id,jdbcType=INTEGER}, </if> <if test="username != null" > #{username,jdbcType=VARCHAR}, </if> <if test="birthday != null" > #{birthday,jdbcType=DATE}, </if> <if test="sex != null" > #{sex,jdbcType=CHAR}, </if> <if test="address != null" > #{address,jdbcType=VARCHAR}, </if> </trim> </insert> <select id="countByExample" parameterType="app.domain.UserExample" resultType="java.lang.Integer" > select count(*) from user <if test="_parameter != null" > <include refid="Example_Where_Clause" /> </if> </select> <update id="updateByExampleSelective" parameterType="map" > update user <set > <if test="record.id != null" > id = #{record.id,jdbcType=INTEGER}, </if> <if test="record.username != null" > username = #{record.username,jdbcType=VARCHAR}, </if> <if test="record.birthday != null" > birthday = #{record.birthday,jdbcType=DATE}, </if> <if test="record.sex != null" > sex = #{record.sex,jdbcType=CHAR}, </if> <if test="record.address != null" > address = #{record.address,jdbcType=VARCHAR}, </if> </set> <if test="_parameter != null" > <include refid="Update_By_Example_Where_Clause" /> </if> </update> <update id="updateByExample" parameterType="map" > update user set id = #{record.id,jdbcType=INTEGER}, username = #{record.username,jdbcType=VARCHAR}, birthday = #{record.birthday,jdbcType=DATE}, sex = #{record.sex,jdbcType=CHAR}, address = #{record.address,jdbcType=VARCHAR} <if test="_parameter != null" > <include refid="Update_By_Example_Where_Clause" /> </if> </update> <update id="updateByPrimaryKeySelective" parameterType="app.domain.User" > update user <set > <if test="username != null" > username = #{username,jdbcType=VARCHAR}, </if> <if test="birthday != null" > birthday = #{birthday,jdbcType=DATE}, </if> <if test="sex != null" > sex = #{sex,jdbcType=CHAR}, </if> <if test="address != null" > address = #{address,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> <update id="updateByPrimaryKey" parameterType="app.domain.User" > update user set username = #{username,jdbcType=VARCHAR}, birthday = #{birthday,jdbcType=DATE}, sex = #{sex,jdbcType=CHAR}, address = #{address,jdbcType=VARCHAR} where id = #{id,jdbcType=INTEGER} </update> </mapper> |
将XxxMapper.java的文件拷贝至mapper目录内。
注意:XxxMapper xml文件和XxxMapper.java文件在一个目录内且文件名相同。
XxxMapper.java |
int countByExample(UserExampleexample); //返回符合条件的记录数 int deleteByExample(UserExampleexample); //删除符合条件的记录 int deleteByPrimaryKey(Integerid); //根据主键删除 int insert(Userrecord); //插入对象所有字段 int insertSelective(Userrecord); //插入对象不为空的字段(不包括大对象) List<Items> selectByExampleWithBLOBs(ItemsExample example);//插入对象不为空的字段(包括大对象) List<User> selectByExample(UserExampleexample); //自定义查询条件查询结果集 User selectByPrimaryKey(Integerid); //根据主键查询 int updateByExampleSelective(@Param("record") Userrecord, @Param("example") UserExample example); //根据条件将对象中不为空的值更新至数据库 int updateByExampleWithBLOBs(@Param("record") Itemsrecord, @Param("example") ItemsExample example); //根据条件将对象中所有字段(包括大对象)更新至数据库 int updateByExample(@Param("record") Userrecord, @Param("example") UserExample example); //根据条件将对象中不为空的值更新至数据库 int updateByPrimaryKeySelective(Userrecord); //根据主键将对象中所有字段更新至数据库 int updateByPrimaryKeyWithBLOBs(Itemsrecord); //根据主键将对象中所有字段(包括大对象)更新至数据库 int updateByPrimaryKey(Userrecord); //根据主键将对象中所有字段的值更新至数据库 |
学会使用mapper自动生成的增、删、改、查方法。
测试:
ItemsMapperTest.java |
package app.mapper; import java.util.List; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import app.domain.Items; import app.domain.ItemsExample; public class ItemsMapperTest { private ApplicationContextapplicationContext; private ItemsMapper itemsMapper; @Before//在setUp这个方法得到spring容器 public void setUp()throws Exception { applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml"); itemsMapper = (ItemsMapper) applicationContext.getBean("itemsMapper"); } @Test//根据主键删除 public void testDeleteByPrimaryKey() { // } @Test//插入 public void testInsert() { Items items = new Items();//构造items对象 items.setName("手机"); items.setPrice(999f); itemsMapper.insert(items); } @Test//自定义条件查询 public void testSelectByExample() { ItemsExample itemsExample = new ItemsExample(); itemsExample.setDistinct(true); //使用DISTINCT查询 //通过criteria构造查询条件 ItemsExample.Criteria criteria = itemsExample.createCriteria(); criteria.andNameEqualTo("笔记本").andPriceLessThan(3000F); //通过criteria构造查询条件 ItemsExample.Criteria criteria2 = itemsExample.createCriteria(); criteria2.andPriceGreaterThan(3000F); itemsExample.or(criteria2); //使用OR条件查询 List<Items> list = itemsMapper.selectByExample(itemsExample); //可能返回多条记录 System.out.println(list); } @Test//根据主键查询 public void testSelectByPrimaryKey() { Items items = itemsMapper.selectByPrimaryKey(1); System.out.println(items); } @Test//更新数据 public void testUpdateByPrimaryKey() { //对所有字段进行更新,需要先查询出来再更新 Items items = itemsMapper.selectByPrimaryKey(1); items.setName("水杯"); itemsMapper.updateByPrimaryKey(items); //如果传入字段不空为才更新相应字段,在批量更新中使用此方法,不需要先查询再更新 //itemsMapper.updateByPrimaryKeySelective(record); } } |
XxxMapper.xml文件已经存在时,如果进行重新生成则mapper.xml文件内容不被覆盖而是进行内容追加,结果导致MyBatis解析失败。
解决方法:删除原来已经生成的mapper xml文件再进行生成。
MyBatis自动生成的PO及mapper.java文件不是内容而是直接覆盖没有此问题。
下边是关于针对oracle数据库表生成代码的schema问题:
Schma即数据库模式,oracle中一个用户对应一个schema,可以理解为用户就是schema。
当Oralce数据库存在多个schema可以访问相同的表名时,使用MyBatis生成该表的mapper.xml将会出现mapper.xml内容重复的问题,结果导致MyBatis解析错误。
解决方法:在table中填写schema,如下:
<table schema="XXXX" tableName=" " >
XXXX即为一个schema的名称,生成后将mapper.xml的schema前缀批量去掉,如果不去掉当oracle用户变更了sql语句将查询失败。
快捷操作方式:mapper.xml文件中批量替换:“from XXXX.”为空
Oracle查询对象的schema可从dba_objects中查询,如下:
select *from dba_objects
上一篇博文MyBatis快速入门中我们讲了如何使用Mybatis查询users表中的数据,算是对MyBatis有一个初步的入门了,下面讲解一下如何使用MyBatis对users表执行CRUD操作。本文中使用到的测试环境是上一篇博文中的测试环境。
1、定义sql映射xml文件
userMapper.xml文件的内容如下:
<?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"> <mappernamespace="app.mapping.userMapper"> <!-- 根据id查询得到一个user对象 --> <selectid="getUser" parameterType="int" resultType="app.domain.User"> select * from users where id=#{id} </select> <!-- 创建用户(Create) --> <insertid="addUser" parameterType="app.domain.User"> insert into users(name,age) values(#{name},#{age}) </insert> <!-- 删除用户(Remove) --> <deleteid="deleteUser" parameterType="int"> delete from users where id=#{id} </delete> <!-- 修改用户(Update) --> <updateid="updateUser" parameterType="app.domain.User"> update users set name=#{name},age=#{age} where id=#{id} </update> <!-- 查询全部用户--> <selectid="getAllUsers" resultType="app.domain.User"> select * from users </select> </mapper> |
单元测试类代码如下:
package app.test; import java.util.List; import app.domain.User; import app.util.MyBatisUtil; import org.apache.ibatis.session.SqlSession; import org.junit.Test; public class TestCRUDByXmlMapper { @Test public void testAdd(){ //SqlSession sqlSession = MyBatisUtil.getSqlSession(false); SqlSession sqlSession = MyBatisUtil.getSqlSession(true); /* *映射sql的标识字符串: * app.mapping.userMapper是userMapper.xml文件中mapper标签的namespace属性的值, * addUser是insert标签的id属性值,通过insert标签的id属性值就可以找到要执行的SQL */ String statement = "app.mapping.userMapper.addUser";//映射sql的标识字符串 User user = new User(); user.setName("用户孤傲苍狼"); user.setAge(20); //执行插入操作 int retResult = sqlSession.insert(statement,user); //手动提交事务 //sqlSession.commit(); //使用SqlSession执行完SQL之后需要关闭SqlSession sqlSession.close(); System.out.println(retResult); } @Test public void testUpdate(){ SqlSession sqlSession = MyBatisUtil.getSqlSession(true); String statement = "app.mapping.userMapper.updateUser";//映射sql的标识字符串 User user = new User(); user.setId(3); user.setName("孤傲苍狼"); user.setAge(25); //执行修改操作 int retResult = sqlSession.update(statement,user); //使用SqlSession执行完SQL之后需要关闭SqlSession sqlSession.close(); System.out.println(retResult); } @Test public void testDelete(){ SqlSession sqlSession = MyBatisUtil.getSqlSession(true); String statement = "app.mapping.userMapper.deleteUser";//映射sql的标识字符串 //执行删除操作 int retResult = sqlSession.delete(statement,5); //使用SqlSession执行完SQL之后需要关闭SqlSession sqlSession.close(); System.out.println(retResult); } @Test public void testGetAll(){ SqlSession sqlSession = MyBatisUtil.getSqlSession(); String statement = "app.mapping.userMapper.getAllUsers";//映射sql的标识字符串 //执行查询操作,将查询结果自动封装成List<User>返回 List<User> lstUsers = sqlSession.selectList(statement); //使用SqlSession执行完SQL之后需要关闭SqlSession sqlSession.close(); System.out.println(lstUsers); } } |
1、定义sql映射的接口
UserMapperI接口的代码如下:
package app.mapping; import java.util.List; import app.domain.User; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; /** * 定义sql映射的接口,使用注解指明方法要执行的SQL */ public interface UserMapperI { //使用@Insert注解指明add方法要执行的SQL @Insert("insert into users(name, age) values(#{name}, #{age})") public int add(User user); //使用@Delete注解指明deleteById方法要执行的SQL @Delete("delete from users where id=#{id}") public int deleteById(int id); //使用@Update注解指明update方法要执行的SQL @Update("update users set name=#{name},age=#{age} where id=#{id}") public int update(User user); //使用@Select注解指明getById方法要执行的SQL @Select("select * from users where id=#{id}") public User getById(int id); //使用@Select注解指明getAll方法要执行的SQL @Select("select * from users") public List<User> getAll(); } |
需要说明的是,我们不需要针对UserMapperI接口去编写具体的实现类代码,这个具体的实现类由MyBatis帮我们动态构建出来,我们只需要直接拿来使用即可。
2、在conf.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> <environmentsdefault="development"> <environmentid="development"> <transactionManagertype="JDBC" /> <!--配置数据库连接信息 --> <dataSourcetype="POOLED"> <propertyname="driver" value="com.mysql.jdbc.Driver" /> <propertyname="url" value="jdbc:mysql://localhost:3306/mybatis" /> <propertyname="username" value="root" /> <propertyname="password" value="root" /> </dataSource> </environment> </environments> <mappers> <!--注册userMapper.xml文件:userMapper.xml位于app.mapping这个包下,所以resource写成app/mapping/userMapper.xml --> <mapperresource="app/mapping/userMapper.xml"/> <!--注册UserMapper映射接口--> <mapperclass="app.mapping.UserMapperI"/> </mappers> </configuration> |
单元测试类的代码如下:
package app.test; import java.util.List; import app.domain.User; import app.mapping.UserMapperI; import app.util.MyBatisUtil; import org.apache.ibatis.session.SqlSession; import org.junit.Test; public class TestCRUDByAnnotationMapper { @Test public void testAdd(){ SqlSession sqlSession = MyBatisUtil.getSqlSession(true); //得到UserMapperI接口的实现类对象,UserMapperI接口的实现类对象由sqlSession.getMapper(UserMapperI.class)动态构建出来 UserMapperI mapper = sqlSession.getMapper(UserMapperI.class); User user = new User(); user.setName("用户root"); user.setAge(20); int add = mapper.add(user); //使用SqlSession执行完SQL之后需要关闭SqlSession sqlSession.close(); System.out.println(add); } @Test public void testUpdate(){ SqlSession sqlSession = MyBatisUtil.getSqlSession(true); //得到UserMapperI接口的实现类对象,UserMapperI接口的实现类对象由sqlSession.getMapper(UserMapperI.class)动态构建出来 UserMapperI mapper = sqlSession.getMapper(UserMapperI.class); User user = new User(); user.setId(3); user.setName("孤傲苍狼_root"); user.setAge(26); //执行修改操作 int retResult = mapper.update(user); //使用SqlSession执行完SQL之后需要关闭SqlSession sqlSession.close(); System.out.println(retResult); } @Test public void testDelete(){ SqlSession sqlSession = MyBatisUtil.getSqlSession(true); //得到UserMapperI接口的实现类对象,UserMapperI接口的实现类对象由sqlSession.getMapper(UserMapperI.class)动态构建出来 UserMapperI mapper = sqlSession.getMapper(UserMapperI.class); //执行删除操作 int retResult = mapper.deleteById(7); //使用SqlSession执行完SQL之后需要关闭SqlSession sqlSession.close(); System.out.println(retResult); } @Test public void testGetUser(){ SqlSession sqlSession = MyBatisUtil.getSqlSession(); //得到UserMapperI接口的实现类对象,UserMapperI接口的实现类对象由sqlSession.getMapper(UserMapperI.class)动态构建出来 UserMapperI mapper = sqlSession.getMapper(UserMapperI.class); //执行查询操作,将查询结果自动封装成User返回 User user = mapper.getById(8); //使用SqlSession执行完SQL之后需要关闭SqlSession sqlSession.close(); System.out.println(user); } @Test public void testGetAll(){ SqlSession sqlSession = MyBatisUtil.getSqlSession(); //得到UserMapperI接口的实现类对象,UserMapperI接口的实现类对象由sqlSession.getMapper(UserMapperI.class)动态构建出来 UserMapperI mapper = sqlSession.getMapper(UserMapperI.class); //执行查询操作,将查询结果自动封装成List<User>返回 List<User> lstUsers = mapper.getAll(); //使用SqlSession执行完SQL之后需要关闭SqlSession sqlSession.close(); System.out.println(lstUsers); } } |
用到的MyBatisUtil工具类代码如下:
package app.util; import java.io.InputStream; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class MyBatisUtil { /** * 获取SqlSessionFactory * @return SqlSessionFactory */ public static SqlSessionFactory getSqlSessionFactory() { String resource = "conf.xml"; InputStream is = MyBatisUtil.class.getClassLoader().getResourceAsStream(resource); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); return factory; } /** * 获取SqlSession * @return SqlSession */ public static SqlSession getSqlSession() { return getSqlSessionFactory().openSession(); } /** * 获取SqlSession * @param isAutoCommit * true表示创建的SqlSession对象在执行完SQL之后会自动提交事务 * false表示创建的SqlSession对象在执行完SQL之后不会自动提交事务,这时就需要我们手动调用sqlSession.commit()提交事务 * @return SqlSession */ public static SqlSession getSqlSession(boolean isAutoCommit) { return getSqlSessionFactory().openSession(isAutoCommit); } } |
以上的相关代码是全部测试通过的,关于使用MyBatis对表执行CRUD操作的内容就这么多。
之前,我们是直接将数据库的连接配置信息写在了MyBatis的conf.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> <environmentsdefault="development"> <environmentid="development"> <transactionManagertype="JDBC" /> <!--配置数据库连接信息 --> <dataSourcetype="POOLED"> <propertyname="driver" value="com.mysql.jdbc.Driver" /> <propertyname="url" value="jdbc:mysql://localhost:3306/mybatis" /> <propertyname="username" value="root" /> <propertyname="password" value="root" /> </dataSource> </environment> </environments> </configuration> |
其实我们完全可以将数据库的连接配置信息写在一个properties文件中,然后在conf.xml文件中引用properties文件,具体做法如下:
1、在src目录下新建一个db.properties文件,如下图所示:
在db.properties文件编写连接数据库需要使用到的数据库驱动,连接URL地址,用户名,密码,如下:
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis name=root password=root |
2、在MyBatis的conf.xml文件中引用db.properties文件,如下:
<?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> <!-- 引用db.properties配置文件--> <propertiesresource="db.properties"/> <!-- development :开发模式 work :工作模式 --> <environmentsdefault="development"> <environmentid="development"> <transactionManagertype="JDBC" /> <!--配置数据库连接信息 --> <dataSourcetype="POOLED"> <!-- value属性值引用db.properties配置文件中配置的值--> <propertyname="driver" value="${driver}" /> <propertyname="url" value="${url}" /> <propertyname="username" value="${name}" /> <propertyname="password" value="${password}" /> </dataSource> </environment> </environments> </configuration> |
之前,我们在sql映射xml文件中的引用实体类时,需要写上实体类的全类名(包名+类名),如下:
<!-- 创建用户(Create) --> <insertid="addUser" parameterType="app.domain.User"> insert into users(name,age) values(#{name},#{age}) </insert> |
parameterType="app.domain.User"这里写的实体类User的全类名app.domain.User,每次都写这么一长串内容挺麻烦的,而我们希望能够简写成下面的形式
<insertid="addUser2" parameterType="_User"> insert into users(name,age) values(#{name},#{age}) </insert> |
parameterType="_User"这样写就简单多了,为了达到这种效果,我们需要在conf.xml文件中为实体类="app.domain.User"定义一个别名为"_User",具体做法如下:
在conf.xml文件中<configuration></configuration>标签中添加如下配置:
<typeAliases> <typeAliastype="app.domain.User" alias="_User"/> </typeAliases> |
这样就可以为app.domain.User类定义了一个别名为_User,以后_User就代表了app.domain.User类,这样sql映射xml文件中的凡是需要引用app.domain.User类的地方都可以使用_User来代替,这就达到了一个简化实体类引用的目的。
除了可以使用<typeAlias type="app.domain.User" alias="_User"/>这种方式单独为某一个实体类设置别名之外,我们还可以使用如下的方式批量为某个包下的所有实体类设置别名,如下:
<!-- 配置实体类的别名,配置实体类别名的目的是为了在引用实体类时可以使用实体类的别名来代替实体类,达到简写的目的 --> <typeAliases> <!-- 为app.domain包下的所有实体类配置别名,MyBatis默认的设置别名的方式就是去除类所在的包后的简单的类名:比如app.domain.User这个实体类的别名就会被设置成User --> <packagename="app.domain"/> </typeAliases> |
<package name="app.domain"/>就表示为这个包下面的所有实体类设置别名。MyBatis默认的设置别名的方式就是去除类所在的包后的简单的类名,比如app.domain.User这个实体类的别名就会被设置成User。
根据班级id查询班级信息(带老师的信息)
创建一张教师表和班级表,这里我们假设一个老师只负责教一个班,那么老师和班级之间的关系就是一种一对一的关系。
CREATE TABLE teacher( t_id INT PRIMARY KEY AUTO_INCREMENT, t_name VARCHAR(20) ); CREATE TABLE class( c_id INT PRIMARY KEY AUTO_INCREMENT, c_name VARCHAR(20), teacher_id INT ); ALTER TABLE classADD CONSTRAINT fk_teacher_idFOREIGN KEY (teacher_id)REFERENCES teacher(t_id);
INSERT INTO teacher(t_name)VALUES(‘teacher1‘); INSERT INTO teacher(t_name)VALUES(‘teacher2‘); INSERT INTO class(c_name, teacher_id)VALUES(‘class_a‘,1); INSERT INTO class(c_name, teacher_id)VALUES(‘class_b‘,2); |
表之间的关系如下:
1、Teacher类,Teacher类是teacher表对应的实体类。
package app.domain; /** * 定义teacher表对应的实体类 */ public class Teacher { //定义实体类的属性,与teacher表中的字段对应 private int id; //id===>t_id private String name; //name===>t_name // getter, setter & toString } |
2、Classes类,Classes类是class表对应的实体类
package app.domain; /** * 定义class表对应的实体类 */ public class Classes { //定义实体类的属性,与class表中的字段对应 private int id; //id===>c_id private String name; //name===>c_name /** * class表中有一个teacher_id字段,所以在Classes类中定义一个teacher属性, * 用于维护teacher和class之间的一对一关系,通过这个teacher属性就可以知道这个班级是由哪个老师负责的 */ private Teacher teacher; // getter, setter & toString } |
<?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,namespace的值习惯上设置成包名+sql映射文件名,这样就能够保证namespace的值是唯一的 例如namespace="app.mapping.classMapper"就是app.mapping(包名)+classMapper(classMapper.xml文件去除后缀) --> <mappernamespace="app.mapping.classMapper"> <!-- 根据班级id查询班级信息(带老师的信息) ##1.联表查询 SELECT * FROM class c,teacher t WHERE c.teacher_id=t.t_id AND c.c_id=1; ##2.执行两次查询 SELECT * FROM class WHERE c_id=1; //teacher_id=1 SELECT * FROM teacher WHERE t_id=1;//使用上面得到的teacher_id --> <!-- 方式一:嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集 封装联表查询的数据(去除重复的数据) select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=1 --> <selectid="getClass" parameterType="int" resultMap="ClassResultMap"> select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=#{id} </select> <!-- 使用resultMap映射实体类和字段之间的一一对应关系--> <resultMaptype="app.domain.Classes" id="ClassResultMap"> <idproperty="id" column="c_id"/> <resultproperty="name" column="c_name"/> <association property="teacher" javaType="app.domain.Teacher"> <idproperty="id" column="t_id"/> <resultproperty="name" column="t_name"/> </association> </resultMap> <!-- 方式二:嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型 SELECT * FROM class WHERE c_id=1; SELECT * FROM teacher WHERE t_id=1 //1是上一个查询得到的teacher_id的值 --> <selectid="getClass2" parameterType="int" resultMap="ClassResultMap2"> select * from class where c_id=#{id} </select> <!--使用resultMap映射实体类和字段之间的一一对应关系--> <resultMaptype="app.domain.Classes" id="ClassResultMap2"> <idproperty="id" column="c_id"/> <resultproperty="name" column="c_name"/> <association property="teacher" column="teacher_id" select="getTeacher"/> </resultMap> <select id="getTeacher" parameterType="int" resultType="app.domain.Teacher"> SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id} </select> </mapper> |
在conf.xml文件中注册classMapper.xml
<mappers> <!--注册classMapper.xml文件, classMapper.xml位于app.mapping这个包下,所以resource写成app/mapping/classMapper.xml--> <mapperresource="app/mapping/classMapper.xml"/> </ mappers > |
package app.test; import app.domain.Classes; import app.util.MyBatisUtil; import org.apache.ibatis.session.SqlSession; import org.junit.Test; public class Test3 { @Test public void testGetClass(){ SqlSession sqlSession = MyBatisUtil.getSqlSession(); /** * 映射sql的标识字符串, * app.mapping.classMapper是classMapper.xml文件中mapper标签的namespace属性的值, * getClass是select标签的id属性值,通过select标签的id属性值就可以找到要执行的SQL */ String statement = "app.mapping.classMapper.getClass";//映射sql的标识字符串 //执行查询操作,将查询结果自动封装成Classes对象返回 Classes clazz = sqlSession.selectOne(statement,1);//查询class表中id为1的记录 //使用SqlSession执行完SQL之后需要关闭SqlSession sqlSession.close(); System.out.println(clazz);//打印结果:Classes [id=1, name=class_a, teacher=Teacher [id=1, name=teacher1]] } @Test public void testGetClass2(){ SqlSession sqlSession = MyBatisUtil.getSqlSession(); /** * 映射sql的标识字符串, * app.mapping.classMapper是classMapper.xml文件中mapper标签的namespace属性的值, * getClass2是select标签的id属性值,通过select标签的id属性值就可以找到要执行的SQL */ String statement = "app.mapping.classMapper.getClass2";//映射sql的标识字符串 //执行查询操作,将查询结果自动封装成Classes对象返回 Classes clazz = sqlSession.selectOne(statement,1);//查询class表中id为1的记录 //使用SqlSession执行完SQL之后需要关闭SqlSession sqlSession.close(); System.out.println(clazz);//打印结果:Classes [id=1, name=class_a, teacher=Teacher [id=1, name=teacher1]] } } |
MyBatis中使用association标签来解决一对一的关联查询,association标签可用的属性如下:
· property:对象属性的名称
· javaType:对象属性的类型
· column:所对应的外键字段名称
· select:使用另一个查询封装的结果
根据classId查询对应的班级信息,包括学生,老师
在上面的一对一关联查询演示中,我们已经创建了班级表和教师表,因此这里再创建一张学生表
CREATE TABLE student( s_id INT PRIMARY KEY AUTO_INCREMENT, s_name VARCHAR(20), class_id INT ); INSERT INTO student(s_name, class_id)VALUES(‘student_A‘,1); INSERT INTO student(s_name, class_id)VALUES(‘student_B‘,1); INSERT INTO student(s_name, class_id)VALUES(‘student_C‘,1); INSERT INTO student(s_name, class_id)VALUES(‘student_D‘,2); INSERT INTO student(s_name, class_id)VALUES(‘student_E‘,2); INSERT INTO student(s_name, class_id)VALUES(‘student_F‘,2); |
1、Student类
package app.domain; /** * 定义student表所对应的实体类 */ public class Student { //定义属性,和student表中的字段对应 private int id; //id===>s_id private String name; //name===>s_name // getter, setter & toString } |
2、修改Classes类,添加一个List<Student> students属性,使用一个List<Student>集合属性表示班级拥有的学生,如下:
package app.domain; import java.util.List; /** * 定义class表对应的实体类 */ public class Classes { //定义实体类的属性,与class表中的字段对应 private int id; //id===>c_id private String name; //name===>c_name /** * class表中有一个teacher_id字段,所以在Classes类中定义一个teacher属性, * 用于维护teacher和class之间的一对一关系,通过这个teacher属性就可以知道这个班级是由哪个老师负责的 */ private Teacher teacher; //使用一个List<Student>集合属性表示班级拥有的学生 private List<Student> students; // getter, setter & toString } |
添加如下的SQL映射信息
<!-- 根据classId查询对应的班级信息,包括学生,老师 --> <!-- 方式一: 嵌套结果: 使用嵌套结果映射来处理重复的联合结果的子集 SELECT * FROM class c, teacher t,student s WHERE c.teacher_id=t.t_id AND c.C_id=s.class_id AND c.c_id=1 --> <selectid="getClass3" parameterType="int" resultMap="ClassResultMap3"> select * from class c, teacher t,student s where c.teacher_id=t.t_id and c.C_id=s.class_id and c.c_id=#{id} </select> <resultMaptype="app.domain.Classes" id="ClassResultMap3"> <idproperty="id" column="c_id"/> <resultproperty="name" column="c_name"/> <association property="teacher" column="teacher_id" javaType="app.domain.Teacher"> <idproperty="id" column="t_id"/> <resultproperty="name" column="t_name"/> </association> <!-- ofType指定students集合中的对象类型 --> <collection property="students" ofType="app.domain.Student"> <idproperty="id" column="s_id"/> <resultproperty="name" column="s_name"/> </collection> </resultMap> <!-- 方式二:嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型 SELECT * FROM class WHERE c_id=1; SELECT * FROM teacher WHERE t_id=1 //1 是上一个查询得到的teacher_id的值 SELECT * FROM student WHERE class_id=1 //1是第一个查询得到的c_id字段的值 --> <selectid="getClass4" parameterType="int" resultMap="ClassResultMap4"> select * from class where c_id=#{id} </select> <resultMaptype="app.domain.Classes" id="ClassResultMap4"> <idproperty="id" column="c_id"/> <resultproperty="name" column="c_name"/> <association property="teacher" column="teacher_id" javaType="app.domain.Teacher" select="getTeacher2" /> <collection property="students" ofType="app.domain.Student" column="c_id" select="getStudent" /> </resultMap> <select id="getTeacher2" parameterType="int" resultType="app.domain.Teacher"> SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id} </select> <select id="getStudent" parameterType="int" resultType="app.domain.Student"> SELECT s_id id, s_name name FROM student WHERE class_id=#{id} </select> |
package app.test; import app.domain.Classes; import app.util.MyBatisUtil; import org.apache.ibatis.session.SqlSession; import org.junit.Test; public class Test4 { @Test public void testGetClass3(){ SqlSession sqlSession = MyBatisUtil.getSqlSession(); /** * 映射sql的标识字符串, * app.mapping.classMapper是classMapper.xml文件中mapper标签的namespace属性的值, * getClass3是select标签的id属性值,通过select标签的id属性值就可以找到要执行的SQL */ String statement = "app.mapping.classMapper.getClass3";//映射sql的标识字符串 //执行查询操作,将查询结果自动封装成Classes对象返回 Classes clazz = sqlSession.selectOne(statement,1);//查询class表中id为1的记录 //使用SqlSession执行完SQL之后需要关闭SqlSession sqlSession.close(); //打印结果:Classes [id=1, name=class_a, teacher=Teacher [id=1, name=teacher1], students=[Student [id=1, name=student_A], Student [id=2, name=student_B], Student [id=3, name=student_C]]] System.out.println(clazz); } @Test public void testGetClass4(){ SqlSession sqlSession = MyBatisUtil.getSqlSession(); /** * 映射sql的标识字符串, * app.mapping.classMapper是classMapper.xml文件中mapper标签的namespace属性的值, * getClass4是select标签的id属性值,通过select标签的id属性值就可以找到要执行的SQL */ String statement = "app.mapping.classMapper.getClass4";//映射sql的标识字符串 //执行查询操作,将查询结果自动封装成Classes对象返回 Classes clazz = sqlSession.selectOne(statement,1);//查询class表中id为1的记录 //使用SqlSession执行完SQL之后需要关闭SqlSession sqlSession.close(); //打印结果:Classes [id=1, name=class_a, teacher=Teacher [id=1, name=teacher1], students=[Student [id=1, name=student_A], Student [id=2, name=student_B], Student [id=3, name=student_C]]] System.out.println(clazz); } } |
MyBatis中使用collection标签来解决一对多的关联查询,ofType属性指定集合中元素的对象类型。
package app.test; import app.domain.User; import app.util.MyBatisUtil; import org.apache.ibatis.session.SqlSession; import org.junit.Test; /** * @author gacl * 测试一级缓存 */ public class TestOneLevelCache { /* * 一级缓存: 也就Session级的缓存(默认开启) */ @Test public void testCache1() { SqlSession session = MyBatisUtil.getSqlSession(); String statement = "app.mapping.userMapper.getUser"; User user = session.selectOne(statement, 1); System.out.println(user);
/* * 一级缓存默认就会被使用 */ user = session.selectOne(statement, 1); System.out.println(user); session.close(); /* 1. 必须是同一个Session,如果session对象已经close()过了就不可能用了 */ session = MyBatisUtil.getSqlSession(); user = session.selectOne(statement, 1); System.out.println(user); /* 2. 查询条件是一样的 */ user = session.selectOne(statement, 2); System.out.println(user); /* 3. 没有执行过session.clearCache()清理缓存 */ //session.clearCache(); user = session.selectOne(statement, 2); System.out.println(user); /* 4. 没有执行过增删改的操作(这些操作都会清理缓存) */ session.update("app.mapping.userMapper.updateUser", new User(2, "user", 23)); user = session.selectOne(statement, 2); System.out.println(user); } |
1、开启二级缓存,在userMapper.xml文件中添加如下配置
<mappernamespace="app.mapping.userMapper"> <!-- 开启二级缓存 --> <cache/> |
2、测试二级缓存
package app.test; import app.domain.User; import app.util.MyBatisUtil; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.junit.Test; /** * 测试二级缓存 */ public class TestTwoLevelCache { /* * 测试二级缓存 * 使用两个不同的SqlSession对象去执行相同查询条件的查询,第二次查询时不会再发送SQL语句,而是直接从缓存中取出数据 */ @Test public void testCache2() { String statement = "app.mapping.userMapper.getUser"; SqlSessionFactory factory = MyBatisUtil.getSqlSessionFactory(); //开启两个不同的SqlSession SqlSession session1 = factory.openSession(); SqlSession session2 = factory.openSession(); //使用二级缓存时,User类必须实现一个Serializable接口===> User implements Serializable User user = session1.selectOne(statement, 1); session1.commit();//不懂为啥,这个地方一定要提交事务之后二级缓存才会起作用 System.out.println("user="+user); //由于使用的是两个不同的SqlSession对象,所以即使查询条件相同,一级缓存也不会开启使用 user = session2.selectOne(statement, 1); //session2.commit(); System.out.println("user2="+user); } } |
执行如下命令:
mvn archetype:create -DgroupId=app -DartifactId=spring4-mybatis3 -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false |
如下图所示:
创建好的项目如下:
编辑pom.xml文件
<projectxmlns="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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>app</groupId> <artifactId>spring4-mybatis3</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>spring4-mybatis3 Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>spring4-mybatis3</finalName> </build> </project> |
修改 <name>spring4-mybatis3 Maven Webapp</name> 部分,把" Maven Webapp"这部分包含空格的内容去掉,否则Maven在编译项目时会因为空格的原因导致一些莫名其妙的错误出现,修改成: <name>spring4-mybatis3</name> 。
另外,把以下内容删掉:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> |
这部分是junit的jar包依赖信息,这个版本太低了,我们不使用这个Junit测试版本,修改过后的pom.xml内容如下:
<projectxmlns="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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>app</groupId> <artifactId>spring4-mybatis3</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>spring4-mybatis3</name> <url>http://maven.apache.org</url> <dependencies> </dependencies> <build> <finalName>spring4-mybatis3</finalName> </build> </project> |
具体操作步骤如下图所示:
手动创建【src/main/java】、【src/test/resources】、【src/test/java】这三个source folder,如下图所示:
到此,项目搭建的工作就算是全部完成了。
SQL脚本如下:
Create DATABASE spring4_mybatis3; USE spring4_mybatis3;
DROP TABLE IF EXISTS t_user; CREATE TABLE t_user ( user_id char(32)NOT NULL, user_name varchar(30)DEFAULT NULL, user_birthday date DEFAULT NULL, user_salary double DEFAULT NULL, PRIMARY KEY (user_id) ) ENGINE=InnoDBDEFAULT CHARSET=utf8; |
创建好的数据库和表如下:
在网上找到了一个generator工具可以根据创建好的数据库表生成MyBatis的表对应的实体类、SQL映射文件和dao,找到generator工具根目录下的generator.xml文件,这个文件是用来配置代码生成规则的,如下图所示:
编辑generator.xml文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- 数据库驱动包位置 --> <classPathEntrylocation="E:\repository\mysql\mysql-connector-java\5.1.34\mysql-connector-java-5.1.34.jar" /> <!-- <classPathEntry location="C:\oracle\product\10.2.0\db_1\jdbc\lib\ojdbc14.jar" />--> <contextid="DB2Tables" targetRuntime="MyBatis3"> <commentGenerator> <propertyname="suppressAllComments" value="true" /> <!-- 数据库链接URL、用户名、密码 --> <jdbcConnectiondriverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/spring4_mybatis3" userId="root" password="XDP"> <!--<jdbcConnection driverClass="oracle.jdbc.driver.OracleDriver" connectionURL="jdbc:oracle:thin:@localhost:1521:orcl" userId="msa" password="msa">--> </jdbcConnection> <javaTypeResolver> <propertyname="forceBigDecimals" value="false" /> </javaTypeResolver> <!-- 生成实体类的包名和位置,这里配置将生成的实体类放在app.domain这个包下 --> <javaModelGeneratortargetPackage="app.domain" targetProject="C:\Users\gacl\spring4-mybatis3\src\main\java"> <propertyname="enableSubPackages" value="true" /> <propertyname="trimStrings" value="true" /> </javaModelGenerator> <!-- 生成的SQL映射文件包名和位置,这里配置将生成的SQL映射文件放在app.mapping这个包下 --> <sqlMapGeneratortargetPackage="app.mapping" targetProject="C:\Users\gacl\spring4-mybatis3\src\main\java"> <propertyname="enableSubPackages" value="true" /> </sqlMapGenerator> <!-- 生成DAO的包名和位置,这里配置将生成的dao类放在app.dao这个包下 --> <javaClientGeneratortype="XMLMAPPER" targetPackage="app.dao" targetProject="C:\Users\gacl\spring4-mybatis3\src\main\java"> <propertyname="enableSubPackages" value="true" /> </javaClientGenerator> <!-- 要生成那些表(更改tableName和domainObjectName就可以) --> <tabletableName="t_user" domainObjectName="User" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false" /> </context> </generatorConfiguration> |
打开命令行窗口,切换到生成工具的根目录下,执行如下命令:
java -jar mybatis-generator-core-1.3.2.jar -configfile generator.xml -overwrite
如下图所示:
刚才我们在generator.xml文件中配置将生成的代码和SQL映射文件放到"C:\Users\gacl\spring4-mybatis3\src\main\java"这个目录下,这个目录就是我们的spring4-mybatis3项目所在目录,我们刷新一下src/main/java目录,就可以看到生成的代码和映射文件了,如下图所示:
生成的代码和映射文件一行都不用改,可以直接应用到项目当中。下面我们看一眼由generator工具生成的代码和映射文件:
1、生成的dao类
package app.dao; import app.domain.User; public interface UserMapper { int deleteByPrimaryKey(String userId); int insert(User record); int insertSelective(User record); User selectByPrimaryKey(String userId); int updateByPrimaryKeySelective(User record); int updateByPrimaryKey(User record); } |
生成的UserMapper是一个接口,里面定义了一些操作t_user表的增删改查方法。
2、生成的实体类
package app.domain; import java.util.Date; public class User { private String userId; private String userName; private Date userBirthday; private Double userSalary; // getter, setter & toString } |
User类是t_user表的对应的实体类,User类中定义的属性和t_user表中的字段一一对应。
3、生成的SQL映射文件
<?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"> <mappernamespace="app.dao.UserMapper" > <resultMapid="BaseResultMap" type="app.domain.User" > <idcolumn="user_id" property="userId" jdbcType="CHAR" /> <resultcolumn="user_name" property="userName" jdbcType="VARCHAR" /> <resultcolumn="user_birthday" property="userBirthday" jdbcType="DATE" /> <resultcolumn="user_salary" property="userSalary" jdbcType="DOUBLE" /> </resultMap> <sqlid="Base_Column_List" > user_id, user_name, user_birthday, user_salary </sql> <selectid="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.String" > select <includerefid="Base_Column_List" /> from t_user where user_id = #{userId,jdbcType=CHAR} </select> <deleteid="deleteByPrimaryKey" parameterType="java.lang.String" > delete from t_user where user_id = #{userId,jdbcType=CHAR} </delete> <insertid="insert" parameterType="app.domain.User" > insert into t_user (user_id, user_name, user_birthday, user_salary) values (#{userId,jdbcType=CHAR}, #{userName,jdbcType=VARCHAR}, #{userBirthday,jdbcType=DATE}, #{userSalary,jdbcType=DOUBLE}) </insert> <insertid="insertSelective" parameterType="app.domain.User" > insert into t_user <trimprefix="(" suffix=")" suffixOverrides="," > <iftest="userId != null" > user_id, </if> <iftest="userName != null" > user_name, </if> <iftest="userBirthday != null" > user_birthday, </if> <iftest="userSalary != null" > user_salary, </if> </trim> <trimprefix="values (" suffix=")" suffixOverrides="," > <iftest="userId != null" > #{userId,jdbcType=CHAR}, </if> <iftest="userName != null" > #{userName,jdbcType=VARCHAR}, </if> <iftest="userBirthday != null" > #{userBirthday,jdbcType=DATE}, </if> <iftest="userSalary != null" > #{userSalary,jdbcType=DOUBLE}, </if> </trim> </insert> <updateid="updateByPrimaryKeySelective" parameterType="app.domain.User" > update t_user <set> <iftest="userName != null" > user_name = #{userName,jdbcType=VARCHAR}, </if> <iftest="userBirthday != null" > user_birthday = #{userBirthday,jdbcType=DATE}, </if> <iftest="userSalary != null" > user_salary = #{userSalary,jdbcType=DOUBLE}, </if> </set> where user_id = #{userId,jdbcType=CHAR} </update> <updateid="updateByPrimaryKey" parameterType="app.domain.User" > update t_user set user_name = #{userName,jdbcType=VARCHAR}, user_birthday = #{userBirthday,jdbcType=DATE}, user_salary = #{userSalary,jdbcType=DOUBLE} where user_id = #{userId,jdbcType=CHAR} </update> </mapper> |
UserMapper.xml这个文件的内容是编写操作t_user表的SQL语句,重点说一下UserMapper.xml配置中需要注意的几个小细节问题:
1、UserMapper.xml的<mapper>标签的namespace必须是UserMapper接口的全类名,既<mapper namespace="app.dao.UserMapper" >
2、UserMapper.xml的定义操作数据库的<select><delete><update><insert>这些标签的id属性的值必须和UserMapper接口定义的方法名一致,如下图所示:
之所以有上述说的这两点要求,就是为了能够让MyBatis能够根据UserMapper接口和UserMapper.xml文件去自动实现UserMapper接口中定义的相关方法,这样我们就不再需要针对UserMapper接口去编写具体的实现代码了。
首先我们要在项目中加入我们需要的相关jar包,我们可以到Maven的中央仓库:http://search.maven.org/ 找到我们要的相关jar包,如下图所示:
我们只需要在搜索框中输入要找的jar包的名称,点击【SEARCH】按钮,就可以找到我们要的jar包了。
1、添加spring-core,输入spring-core关键字进行查找,如下图所示:
找到关于spring-core的依赖描述信息,如下图所示:
将
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.1.4.RELEASE</version> </dependency> |
复制到项目的pom.xml文件中,如下所示:
<projectxmlns="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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>app</groupId> <artifactId>spring4-mybatis3</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>spring4-mybatis3</name> <url>http://maven.apache.org</url> <dependencies> <!-- 添加Spring4.1.4的核心包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.1.4.RELEASE</version> </dependency> </dependencies> <build> <finalName>spring4-mybatis3</finalName> </build> </project> |
这样Maven就会自动帮我们从Maven的中央仓库中下载spring-core这个jar包到我们的本地仓库,然后将spring-core这个jar包以及它的相关依赖包加入到我们的项目当中,如下所示:
spring4.x与mybatis3.x所需要的相关jar包都可以采用上述所说的方式进行查找,然后添加到项目当中,添加完spring4.x与mybatis3.x相关jar包后,pom.xml文件内容最终如下:
<projectxmlns="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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>app</groupId> <artifactId>spring4-mybatis3</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>spring4-mybatis3</name> <url>http://maven.apache.org</url> <dependencies> <!-- 添加Spring-core包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.1.4.RELEASE</version> </dependency> <!-- 添加spring-context包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.1.4.RELEASE</version> </dependency> <!-- 添加spring-tx包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.1.4.RELEASE</version> </dependency> <!-- 添加spring-jdbc包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.1.4.RELEASE</version> </dependency> <!-- 为了方便进行单元测试,添加spring-test包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.1.4.RELEASE</version> </dependency> <!--添加spring-web包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.1.4.RELEASE</version> </dependency> <!--添加aspectjweaver包 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.5</version> </dependency> <!-- 添加mybatis的核心包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.2.8</version> </dependency> <!-- 添加mybatis与Spring整合的核心包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.2.2</version> </dependency> <!-- 添加servlet3.0核心包 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.2-b01</version> </dependency> <!-- jstl --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- 添加mysql驱动包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.34</version> </dependency> <!-- 添加druid连接池包 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.12</version> </dependency> <!-- 添加junit单元测试包 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>spring4-mybatis3</finalName> </build> </project> |
1、dbconfig.properties
在src/main/resources目录下创建一个dbconfig.properties文件,用于编写连接MySQL数据库的相关信息,dbconfig.properties的内容如下:
driverClassName=com.mysql.jdbc.Driver validationQuery=SELECT 1 jdbc_url=jdbc:mysql://localhost:3306/spring4_mybatis3?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull jdbc_username=root jdbc_password=root |
2、spring.xml(spring框架的配置文件)
在src/main/resources目录下创建一个spring.xml文件,spring.xml文件就是针对Spring框架编写的核心配置文件,spring.xml的内容如下:
<?xml version="1.0" encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 引入dbconfig.properties属性文件 --> <context:property-placeholderlocation="classpath:dbconfig.properties" /> <!-- 自动扫描(自动注入),扫描app.service这个包以及它的子包的所有使用@Service注解标注的类 --> <context:component-scanbase-package="app.service" /> </beans> |
我们的spring.xml文件的配置非常简单,就两个配置。
3、spring-mybatis.xml(spring与mybatis整合的配置文件)
在src/main/resources目录下创建一个spring-mybatis.xml文件,spring-mybatis.xml文件就是针对Spring框架与Mybatis框架整合编写的配置文件,spring-mybatis.xml的内容如下:
<?xml version="1.0" encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<!-- JNDI方式配置数据源 --> <!-- <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="${jndiName}"></property> </bean> --> <!-- ========================================配置数据源========================================= --> <!-- 配置数据源,使用的是alibaba的Druid(德鲁伊)数据源 --> <beanname="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <propertyname="url" value="${jdbc_url}" /> <propertyname="username" value="${jdbc_username}" /> <propertyname="password" value="${jdbc_password}" /> <!-- 初始化连接大小 --> <propertyname="initialSize" value="0" /> <!-- 连接池最大使用连接数量 --> <propertyname="maxActive" value="20" /> <!-- 连接池最大空闲 --> <propertyname="maxIdle" value="20" /> <!-- 连接池最小空闲 --> <propertyname="minIdle" value="0" /> <!-- 获取连接最大等待时间 --> <propertyname="maxWait" value="60000" /> <!-- <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="33" /> --> <propertyname="validationQuery" value="${validationQuery}" /> <propertyname="testOnBorrow" value="false" /> <propertyname="testOnReturn" value="false" /> <propertyname="testWhileIdle" value="true" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <propertyname="timeBetweenEvictionRunsMillis" value="60000" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <propertyname="minEvictableIdleTimeMillis" value="25200000" /> <!-- 打开removeAbandoned功能 --> <propertyname="removeAbandoned" value="true" /> <!-- 1800秒,也就是30分钟 --> <propertyname="removeAbandonedTimeout" value="1800" /> <!-- 关闭abanded连接时输出错误日志 --> <propertyname="logAbandoned" value="true" /> <!-- 监控数据库 --> <!-- <property name="filters" value="stat" /> --> <propertyname="filters" value="mergeStat" /> </bean>
<!-- ========================================分隔线========================================= -->
<!-- ========================================针对myBatis的配置项============================== --> <!-- 配置sqlSessionFactory --> <beanid="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 实例化sqlSessionFactory时需要使用上述配置好的数据源以及SQL映射文件 --> <propertyname="dataSource" ref="dataSource" /> <!-- 自动扫描app/mapping/目录下的所有SQL映射的xml文件, 省掉Configuration.xml里的手工配置 value="classpath:app/mapping/*.xml"指的是classpath(类路径)下app.mapping包中的所有xml文件 UserMapper.xml位于app.mapping包下,这样UserMapper.xml就可以被自动扫描 --> <propertyname="mapperLocations" value="classpath:app/mapping/*.xml" /> </bean> <!-- 配置扫描器 --> <beanclass="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 扫描app.dao这个包以及它的子包下的所有映射接口类 --> <propertyname="basePackage" value="app.dao" /> <propertyname="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean>
<!-- ========================================分隔线========================================= --> <!-- 配置Spring的事务管理器 --> <beanid="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <propertyname="dataSource" ref="dataSource" /> </bean>
<!-- 注解方式配置事物 --> <!-- <tx:annotation-driven transaction-manager="transactionManager" /> -->
<!-- 拦截器方式配置事物 --> <tx:adviceid="transactionAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:methodname="add*" propagation="REQUIRED" /> <tx:methodname="append*" propagation="REQUIRED" /> <tx:methodname="insert*" propagation="REQUIRED" /> <tx:methodname="save*" propagation="REQUIRED" /> <tx:methodname="update*" propagation="REQUIRED" /> <tx:methodname="modify*" propagation="REQUIRED" /> <tx:methodname="edit*" propagation="REQUIRED" /> <tx:methodname="delete*" propagation="REQUIRED" /> <tx:methodname="remove*" propagation="REQUIRED" /> <tx:methodname="repair" propagation="REQUIRED" /> <tx:methodname="delAndRepair" propagation="REQUIRED" />
<tx:methodname="get*" propagation="SUPPORTS" /> <tx:methodname="find*" propagation="SUPPORTS" /> <tx:methodname="load*" propagation="SUPPORTS" /> <tx:methodname="search*" propagation="SUPPORTS" /> <tx:methodname="datagrid*" propagation="SUPPORTS" />
<tx:methodname="*" propagation="SUPPORTS" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcutid="transactionPointcut" expression="execution(* app.service..*Impl.*(..))" /> <aop:advisorpointcut-ref="transactionPointcut" advice-ref="transactionAdvice" /> </aop:config>
<!-- 配置druid监控spring jdbc --> <beanid="druid-stat-interceptor" class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor"> </bean> <beanid="druid-stat-pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut" scope="prototype"> <propertyname="patterns"> <list> <value>app.service.*</value> </list> </property> </bean> <aop:config> <aop:advisoradvice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut" /> </aop:config> </beans> |
到此,相关的配置文件算是编写完成了,如下图所示:
经过以上两个步骤,spring4与mybatis3的整合算是全部完成了。接下来我们要做的工作就算进行单元测试,测试一下spring4与mybatis3的整合是否成功。
1、在src/main/java目录下创建一个app.service包,然后在app.service包创建一个UserServiceI接口,如下所示:
package app.service; import app.domain.User; public interface UserServiceI { /** * 添加用户 * @param user */ void addUser(User user); /** * 根据用户id获取用户 * @param userId * @return */ User getUserById(String userId); } |
2、在src/main/java目录下创建一个app.service.impl包,然后在app.service.impl包创建一个针对UserServiceI接口的实现类:UserServiceImpl,如下所示:
package app.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import app.dao.UserMapper; import app.domain.User; import app.service.UserServiceI; /** * @author gacl * 使用@Service注解将UserServiceImpl类标注为一个service * service的id是userService */ @Service("userService") public class UserServiceImplimplements UserServiceI { /** * 使用@Autowired注解标注userMapper变量, * 当需要使用UserMapper时,Spring就会自动注入UserMapper */ @Autowired private UserMapper userMapper;//注入dao @Override public void addUser(User user) { userMapper.insert(user); } @Override public User getUserById(String userId) { return userMapper.selectByPrimaryKey(userId); } } |
创建好的两个类如下所示:
3、在src/test/java目录下编写单元测试类,新建一个app.test包,然后在这个包下创建一个MyBatisTest类,代码如下:
package app.test; import java.util.Date; import java.util.UUID; import app.domain.User; import app.service.UserServiceI; //import app.service.UserServiceI; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyBatisTest { private UserServiceI userService; /** * 这个before方法在所有的测试方法之前执行,并且只执行一次 * 所有做Junit单元测试时一些初始化工作可以在这个方法里面进行 * 比如在before方法里面初始化ApplicationContext和userService */ @Before public void before(){ //使用"spring.xml"和"spring-mybatis.xml"这两个配置文件创建Spring上下文 ApplicationContext ac = new ClassPathXmlApplicationContext(new String[]{"spring.xml","spring-mybatis.xml"}); //从Spring容器中根据bean的id取出我们要使用的userService对象 userService = (UserServiceI) ac.getBean("userService"); } @Test public void testAddUser(){ //ApplicationContext ac = new ClassPathXmlApplicationContext(new String[]{"spring.xml","spring-mybatis.xml"}); //UserServiceI userService = (UserServiceI) ac.getBean("userService"); User user = new User(); user.setUserId(UUID.randomUUID().toString().replaceAll("-", "")); user.setUserName("白虎神皇xdp"); user.setUserBirthday(new Date()); user.setUserSalary(10000D); userService.addUser(user); } } |
执行单元测试代码,这时候会报如下错误:
错误提示是说没有找到"app.test.MyBatisTest"这个类,这是因为我们没有使用maven编译项目中的类的缘故。
下面我们使用Maven编译项目,选中项目的pom.xml文件→【Debug As】→【maven install】,如下所示:
编译结果如下:
在这里说一下我执行Maven install之后遇到的问题,第一次执行Maven install命令时,就出现了如下一堆乱七八糟的错误:
后来我把项目删掉,再重新导入项目,然后再执行Clean项目操作之后,如下图所示:
再执行Maven install操作又可以正常编译通过了,这让我郁闷了好久,这应该不是我项目配置的原因,而是Maven的原因,具体也不知道为啥会这样。反正这算是一种解决办法吧,如果遇到执行Maven install操作不能正常编译通过的情况:可以尝试采用:Maven clean→Clean项目→Maven install这三个步骤去解决问题。
除了可以用常规的Junit进行单元测试之外,我们还可以使用Spring提供的Junit测试框架进行单元测试,在app.test下新建一个MyBatisTestBySpringTestFramework类,代码如下:
package app.test; import java.util.Date; import java.util.UUID; import app.domain.User; import app.service.UserServiceI; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class) //配置了@ContextConfiguration注解并使用该注解的locations属性指明spring和配置文件之后, @ContextConfiguration(locations = {"classpath:spring.xml", "classpath:spring-mybatis.xml" }) public class MyBatisTestBySpringTestFramework { //注入userService @Autowired private UserServiceI userService; @Test public void testAddUser(){ User user = new User(); user.setUserId(UUID.randomUUID().toString().replaceAll("-", "")); user.setUserName("xdp_gacl_白虎神皇"); user.setUserBirthday(new Date()); user.setUserSalary(10000D); userService.addUser(user); } @Test public void testGetUserById(){ String userId = "fb1c5941094e400b975f10d9a9d602a3"; User user = userService.getUserById(userId); System.out.println(user.getUserName()); } } |
执行这两个测试方法,是可以正常测试通过的,如下所示:
到此,我们框架的整合测试工作就算是全部通过了,整合成功。
1、编辑web.xml文件,添加spring监听器配置项,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <web-appxmlns="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"> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <listener> <description>Spring监听器</description> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- ContextLoaderListener初始化Spring上下文时需要使用到的contextConfigLocation参数 --> <context-param> <param-name>contextConfigLocation</param-name> <!-- 配置spring.xml和spring-mybatis.xml这两个配置文件的位置,固定写法 --> <param-value>classpath:spring.xml,classpath:spring-mybatis.xml</param-value> </context-param> </web-app> |
2、在UserMapper接口中添加一个获取所有用户信息的getAllUser()方法,如下所示:
package app.dao; import java.util.List; import app.domain.User; public interface UserMapper { int deleteByPrimaryKey(String userId); int insert(User record); int insertSelective(User record); User selectByPrimaryKey(String userId); int updateByPrimaryKeySelective(User record); int updateByPrimaryKey(User record); /**获取所有用户信息 * @return List<User> */ List<User> getAllUser(); } |
3、在UserMapper.xml文件中编写getAllUser()方法要执行的SQL语句,如下所示:
<?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"> <mappernamespace="app.dao.UserMapper" > <resultMapid="BaseResultMap" type="app.domain.User" > <idcolumn="user_id" property="userId" jdbcType="CHAR" /> <resultcolumn="user_name" property="userName" jdbcType="VARCHAR" /> <resultcolumn="user_birthday" property="userBirthday" jdbcType="DATE" /> <resultcolumn="user_salary" property="userSalary" jdbcType="DOUBLE" /> </resultMap> <sqlid="Base_Column_List" > user_id, user_name, user_birthday, user_salary </sql> <selectid="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.String" > select <includerefid="Base_Column_List" /> from t_user where user_id = #{userId,jdbcType=CHAR} </select> <deleteid="deleteByPrimaryKey" parameterType="java.lang.String" > delete from t_user where user_id = #{userId,jdbcType=CHAR} </delete> <insertid="insert" parameterType="app.domain.User" > insert into t_user (user_id, user_name, user_birthday, user_salary) values (#{userId,jdbcType=CHAR}, #{userName,jdbcType=VARCHAR}, #{userBirthday,jdbcType=DATE}, #{userSalary,jdbcType=DOUBLE}) </insert> <insertid="insertSelective" parameterType="app.domain.User" > insert into t_user <trimprefix="(" suffix=")" suffixOverrides="," > <iftest="userId != null" > user_id, </if> <iftest="userName != null" > user_name, </if> <iftest="userBirthday != null" > user_birthday, </if> <iftest="userSalary != null" > user_salary, </if> </trim> <trimprefix="values (" suffix=")" suffixOverrides="," > <iftest="userId != null" > #{userId,jdbcType=CHAR}, </if> <iftest="userName != null" > #{userName,jdbcType=VARCHAR}, </if> <iftest="userBirthday != null" > #{userBirthday,jdbcType=DATE}, </if> <iftest="userSalary != null" > #{userSalary,jdbcType=DOUBLE}, </if> </trim> </insert> <updateid="updateByPrimaryKeySelective" parameterType="app.domain.User" > update t_user <set> <iftest="userName != null" > user_name = #{userName,jdbcType=VARCHAR}, </if> <iftest="userBirthday != null" > user_birthday = #{userBirthday,jdbcType=DATE}, </if> <iftest="userSalary != null" > user_salary = #{userSalary,jdbcType=DOUBLE}, </if> </set> where user_id = #{userId,jdbcType=CHAR} </update> <updateid="updateByPrimaryKey" parameterType="app.domain.User" > update t_user set user_name = #{userName,jdbcType=VARCHAR}, user_birthday = #{userBirthday,jdbcType=DATE}, user_salary = #{userSalary,jdbcType=DOUBLE} where user_id = #{userId,jdbcType=CHAR} </update>
<!-- ==============以下内容是根据自身业务扩展的内容======================= --> <!-- select标签的id属性与UserMapper接口中定义的getAllUser方法要一模一样 --> <selectid="getAllUser" resultMap="BaseResultMap"> select user_id, user_name, user_birthday, user_salary from t_user </select> </mapper> |
4、在UserServiceI接口中也添加一个getAllUser()方法,如下:
package app.service; import java.util.List; import app.domain.User; public interface UserServiceI { /** * 添加用户 * @param user */ void addUser(User user); /** * 根据用户id获取用户 * @param userId * @return */ User getUserById(String userId); /**获取所有用户信息 * @return List<User> */ List<User> getAllUser(); } |
、在UserServiceImpl类中实现getAllUser方法,如下:
package app.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import app.dao.UserMapper; import app.domain.User; import app.service.UserServiceI; /** * @author gacl * 使用@Service注解将UserServiceImpl类标注为一个service * service的id是userService */ @Service("userService") public class UserServiceImplimplements UserServiceI { /** * 使用@Autowired注解标注userMapper变量, * 当需要使用UserMapper时,Spring就会自动注入UserMapper */ @Autowired private UserMapper userMapper;//注入dao @Override public void addUser(User user) { userMapper.insert(user); } @Override public User getUserById(String userId) { return userMapper.selectByPrimaryKey(userId); } @Override public List<User> getAllUser() { return userMapper.getAllUser(); } } |
6、在src/main/java目录下创建一个app.web.controller包,然后在app.web.controller下创建一个UserServlet,如下:
package app.web.controller; import java.io.IOException; import java.util.List; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.context.ApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import app.domain.User; import app.service.UserServiceI; /** * @WebServlet是Servlet3.0提供的注解,目的是将一个继承了HttpServlet类的普通java类标注为一个Servlet * UserServlet使用了@WebServlet标注之后,就不需要在web.xml中配置了 */ @WebServlet("/UserServlet") public class UserServletextends HttpServlet { //处理业务逻辑的userService private UserServiceI userService; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取所有的用户信息 List<User> lstUsers = userService.getAllUser(); request.setAttribute("lstUsers", lstUsers); request.getRequestDispatcher("/index.jsp").forward(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } public void init()throws ServletException { //在Servlet初始化时获取Spring上下文对象(ApplicationContext) ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); //从ApplicationContext中获取userService userService = (UserServiceI) ac.getBean("userService"); } } |
7、编辑index.jsp页面,用于展示查询到的用户信息,内容如下:
<%@ page language="java" pageEncoding="UTF-8"%> <%--引入JSTL核心标签库 --%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html> <html> <head> <title>显示用户信息</title> <styletype="text/css"> table,td{ border: 1px solid; border-collapse: collapse; } </style> </head> <body> <table> <tr> <td>用户ID</td> <td>用户名</td> <td>用户生日</td> <td>工资</td> </tr> <%--遍历lstUsers集合中的User对象 --%> <c:forEachvar="user" items="${lstUsers}"> <tr> <td>${user.userId}</td> <td>${user.userName}</td> <td>${user.userBirthday}</td> <td>${user.userSalary}</td> </tr> </c:forEach> </table> </body> </html> |
8、执行maven install命令编译项目,然后将项目部署到tomcat服务器中运行,注意,由于要使用Servlet3.0,所以必须将项目部署到tomcat7.x以上的服务器中去运行,如下所示:
输入地址:http://localhost:8080/spring4-mybatis3/UserServlet 访问UserServlet,访问结果如下:
可以看到,t_user表中的用户信息全部查询出来显示到页面上了。这样在web服务器中的测试也正常通过了。
以上就是Spring4.x与MyBatis3.x整合的全部内容了。编写这个整合例子花了不少时间,使用Maven编译时总是出现莫名其妙的问题,有时候成功,有时候失败,反正很莫名其妙。如果遇到执行Maven install操作不能正常编译通过的情况:可以尝试采用:Maven clean→Clean项目→Maven install这三个步骤去解决问题
http://blog.csdn.net/zoutongyuan/article/details/41379851
一直想写这篇文章,前段时间 痴迷于JavaScript、NodeJs、AngularJs,做了大量的研究,对前后端交互有了更深层次的认识。
今天抽个时间写这篇文章,我有预感,这将是一篇很详细的文章,详细的配置,详细的注释,看起来应该很容易懂。
用最合适的技术去实现,并不断追求最佳实践。这就是架构之道。
希望这篇文章能给你们带来一些帮助,同时希望你们可以为这个项目贡献你的想法。
源码地址:https://github.com/starzou/quick4j点击打开
看我们的项目结构:
是一个典型的Maven项目 :
src/main/java:存放java源文件
src/main/resources:存放程序资源、配置文件
src/test/java:存放测试代码文件
src/main/webapp:web根目录
pom.xml
: maven项目配置文件,管理依赖,编译,打包
主要的后端架构:Spring + Spring MVC + Mybatis + Apache Shiro
前端界面主要使用MetroNic模板,
先看我们搭建完成,跑起来的效果,这样你才有兴趣看下去:
是一个典型的Maven项目 :
src/main/java:存放java源文件
src/main/resources:存放程序资源、配置文件
src/test/java:存放测试代码文件
src/main/webapp:web根目录
pom.xml
: maven项目配置文件,管理依赖,编译,打包
主要的后端架构:Spring + Spring MVC + Mybatis + Apache Shiro
前端界面主要使用MetroNic模板,
先看我们搭建完成,跑起来的效果,这样你才有兴趣看下去:
原文:http://blog.csdn.net/u010779348/article/details/51882176