MyBatis学习笔记(一)
MyBatis概述
MyBatis本是Apache的一个开源项目iBatis, 2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。
MyBatis 是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、 创建connection、创建 statement、手动设置参数、结果集检索等 jdbc繁杂的过程代码。
Mybatis 通过 xml 或注解的方式将要执行的各种 statement(statement、 preparedStatemnt、CallableStatement)配置起来,并通过 java 对象和 statement 中的 sql 进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
使用JDBC存在的问题
JDBC的编程步骤
- 加载数据库驱动
- 创建获取数据库连接
- 创建Statement对象
- 执行SQL语句并返回ResultSet结果集
- 处理结果集
- 释放资源
存在的问题
- 数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能。如果使用数据库连接池可解决此问题。
- SQL语句在代码中硬编码,造成代码不易维护,实际应用中SQL变化的可能较大,SQL语句变动需要改变 java代码。
- 使用preparedStatement向占位符传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
- 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。
MyBatis架构
MyBatis架构图
架构图解释
MyBatis配置文件
-
SqlMapConfig.xml
MyBatis的全局配置文件,配置了MyBatis的运行环境等信息
-
SqlSessionFactory
会话工厂,用于创建SqlSession,通过MyBatis环境等配置信息构造SqlSessionFactory
-
SqlSession
会话,操作数据库主要通过SqlSession进行
-
Executor
MyBatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器
-
MappedStatement
Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。
-
输入映射
Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数
-
输出映射
Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程
-
Mybatis入门程序
MyBatis下载
点击上述链接进入页面后,再点击下图中的位置即可下载;下载完毕后压缩包中有MyBatis的依赖包(在lib目录下)。
环境配置
-
导入MyBatis依赖包以及数据库连接驱动
-
编写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"> <environment id="development"> <!--事务管理,使用jdbc--> <transactionManager type="jdbc"></transactionManager> <!--数据库连接池--> <dataSource type="POOLED"> <!--配置数据库连接信息--> <property name="driver" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/mybatistest?characterEncoding=UTF-8"></property> <property name="username" value="数据库用户名"></property> <property name="password" value="数据库密码"></property> </dataSource> </environment> </environments> </configuration>
-
编写日志输出文件log4j.properties,日志输出文件的编写如下:
# Global logging configuration log4j.rootLogger=DEBUG, stdout # 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
-
在数据库中创建表,我创建的是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=30 DEFAULT CHARSET=utf8;
-
编写数据库中表的对应的pojo(Plain Ordinary Java Object,其实就是Java Bean)
-
编写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 -> <mapper namespace="com.xurenyi.pojo.User"> <!-- id:sql的id parameterType:输入参数的类型 resultType:输出结果的类型,应该填写对应POJO的全路径 #{v}:占位符,相当于jdbc中的? --> <select id="findUserById" parameterType="Integer" resultType="com.xurenyi.pojo.User"> select * from user where id=#{v} </select> </mapper>
-
在SqlMapperConfig.xml中配置UserMapper文件,写法如下:
<!--配置UserMapper.xml文件的位置--> <mappers> <mapper resource="config/UserMapper.xml"></mapper> </mappers>
案例
根据id查询单个用户
- 在UserMapper.xml中编写SQL语句:
<!-- mapper:用于编写SQL语句,mapper元素中可以写多个SQL语句 namespace:命名空间,用于隔离SQL --> <mapper namespace="com.xurenyi.pojo.User"> <!-- id:SQL语句的id parameterType:输入参数的类型 resultType:输出结果的类型,应该填写对应POJO的全路径 '#{v}':占位符,相当于jdbc中的? --> <!-- 根据id查询单个用户 --> <select id="findUserById" parameterType="Integer" resultType="com.xurenyi.pojo.User"> select * from user where id=${value} </select> </mapper>
- 编写测试方法:
//根据id查找单个用户 @Test public void findUserById() throws IOException { //加载SqlMapConfig.xml配置文件,把配置文件的路径作为参数传入 InputStream in = Resources.getResourceAsStream("config/SqlMapConfig.xml"); //创建SqlSessionFactoryBuilder SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); //获取SqlSessionFactory(根据配置文件创建SqlSessionFactory) SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(in); //获取SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //执行select操作,传入参数是SQL语句的id、SQL语句中的查找参数 User user = sqlSession.selectOne("findUserById", 1); System.out.println(user); }
注意: 由于下面的例子中测试代码的前面部分与根据id查询单个用户
中的测试代码是一样的,所以在下述的例子中,就只写SQL语句(写在<mapper></mapper>
元素中)以及通过sqlSession
进行操作数据库的语句(例如根据id查询单个用户
测试代码中的User user = sqlSession.selectOne("findUserById", 1);
这句),省略获取SqlSession对象的代码。
根据用户名模糊查询用户
- 方式一
- SQL语句
<!--模糊查询--> <select id="findUserListByLike" parameterType="String" resultType="com.xurenyi.pojo.User"> select * from user where username like #{v} </select>
- 测试代码
/* 获取SqlSession对象的代码,略 */ List<User> userList = sqlSession.selectList("findUserListByLike", "%张%");
- SQL语句
- 方式二
- SQL语句
<!--模糊查询--> <select id="findUserListByLike" parameterType="String" resultType="com.xurenyi.pojo.User"> select * from user where username like '%${value}%' </select>
- 测试代码
/* 获取SqlSession对象的代码,略 */ List<User> userList = sqlSession.selectList("findUserListByLike", "张");
- SQL语句
- 方式三
- SQL语句
<!--模糊查询--> <select id="findUserListByLike" parameterType="String" resultType="com.xurenyi.pojo.User"> select * from user where username like "%"#{v}"%" </select>
- 测试代码
/* 获取SqlSession对象的代码,略 */ List<User> userList = sqlSession.selectList("findUserListByLike", "张");
- SQL语句
添加用户
- SQL语句
<!--添加用户 参数类型是User对象,所以在SQL语句中取参数值的时候,需要把User实体类里面的属性名写进去,而不能在'#{}'里面随便写几个字母 --> <insert id="addUser" parameterType="com.xurenyi.pojo.User"> <!--取参数的时候需要把User里面的属性写进'#{}',而不能随便写--> insert into user values(#{id},#{username},#{birthday},#{sex},#{address}) </insert>
- 测试代码
/* 获取SqlSession对象的代码,略 */ User user = new User(); user.setId(null); user.setUsername("用户名"); user.setBirthday(new Date()); user.setSex("男"); user.setAddress("用户地址"); // 需要"改变"数据库中数据的操作(例如:插入、删除、修改数据)都有一个int类型的返回值,该返回值表示对数据库中记录影响的行数 int addUser = sqlSession.insert("addUser", user); sqlSession.commit(); //需要自己提交事务(在增删改查这四个操作中,除了查询操作,其余三个操作都需要提交事务)
添加用户返回ID
- 需求:在使用MyBatis插入一个用户数据以后,获取该用户的id。
- select LAST_INSERT_ID();
- 这是MySQL中提供的查询最新添加数据的id,但是执行该条语句时,需要先执行一条插入语句才能返回id,否则就返回0。
- SQL语句
<insert id="addUser" parameterType="com.xurenyi.pojo.User"> <!-- 标签实现主键返回 keyColumn:主键对应数据库表中的哪一列 keyProperty:主键对应pojo中的哪一个属性 order:设置在执行insert语句之前执行查询id的操作,还是在执行insert语句之后执行查询id的操作 --> <selectKey keyColumn="id" keyProperty="id" resultType="Integer" order="AFTER"> select LAST_INSERT_ID() </selectKey> <!--取参数的时候需要把User里面的属性写进#{},而不能随便写--> insert into user values(#{id},#{username},#{birthday},#{sex},#{address}) </insert>
- 测试代码
/* 获取SqlSession对象的代码,略 */ User user = new User(); user.setId(null); user.setUsername("用户名"); user.setBirthday(new Date()); user.setSex("男"); user.setAddress("用户地址"); // 需要"改变"数据库中数据的操作(例如:插入、删除、修改数据)都有一个int类型的返回值,该返回值表示对数据库中记录影响的行数 int addUser = sqlSession.insert("addUser", user); sqlSession.commit(); //需要自己提交事务(在增删改查这四个操作中,除了查询操作,其余三个操作都需要提交事务) System.out.println(user.getId()); //在提交事务之后获取id并打印
根据id修改用户数据
- SQL语句
<!--根据id修改用户数据--> <update id="updateUserById" parameterType="com.xurenyi.pojo.User"> update user set address=#{address},birthday=#{birthday} where id=#{id} </update>
- 测试代码
/* 获取SqlSession对象的代码,略 */ User user = new User(); user.setId(28); user.setAddress("修改后的地址"); user.setUsername("修改后的姓名"); user.setSex("女"); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); Date date = simpleDateFormat.parse("1994-01-01 17:06:00"); user.setBirthday(date); sqlSession.update("updateUserById", user); //提交事务 sqlSession.commit(); //关闭sqlSession sqlSession.close();
根据id删除用户
- SQL语句
<!--根据id删除用户数据--> <delete id="deleteUserById" parameterType="Integer"> delete from user where id=#{id} </delete>
- 测试代码
/* 获取SqlSession对象的代码,略 */ int count=sqlSession.delete("deleteUserById",28); //提交事务 sqlSession.commit(); System.out.println(count); //关闭sqlSession sqlSession.close();
小结
- #{}和${}的区别
- #{}
#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换。#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值, #{}括号中可以是 value 或其它名称。 - $ {}
$ {}表示拼接 sql 串,通过$ {}可以将parameterType传入的内容拼接在sql中且不进行 jdbc类型转换,$ {}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是 value。
- #{}
- parameterType和resultType
- parameterType
指定输入参数类型,mybatis通过ognl从输入对象中获取参数值拼接在 sql 中。 - resultType
指定输出结果类型,mybatis 将 sql 查询结果的一行记录数据映射为resultType 指定类型的对象。如果有多条数据,则分别进行映射,并把对象放到容器 List 中。
- parameterType
- selectOne和selectList
- selectOne
selectOne用于查询一条记录,如果使用selectOne查询多条记录会报错。 - selectList
selectList用于查询一条或多条记录。
- selectOne
- MyBatis解决JDBC编程的问题
-
问题1
- 描述:数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题
- 解决:在 SqlMapConfig.xml 中配置数据连接池,使用连接池管理数据库链接
-
问题2
- 描述:Sql 语句写在代码中造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java代码
- 解决:将 Sql 语句配置在 XXXXmapper.xml 文件中与 java 代码分离
-
问题3
- 描述:向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应
- 解决:Mybatis 自动将 java 对象映射至 sql 语句,通过 statement 中的 parameterType 定义输入参数的类型
-
问题4
- 描述:对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果 能将数据库记录封装成pojo对象解析比较方便
- 解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的 resultType定义输出结果的类型
-
- MyBatis和Hibernate的异同
- Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。
- Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如:互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一旦需求变化要求成果输出迅速。但是灵活的前提是 mybatis 无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套 sql 映射文件,工作量大。
- Hibernate 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
- 总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。
SqlMapConfig.xml配置文件详解
SqlMapConfig.xml中配置的内容和顺序
这里选讲几个元素。
-
properties(属性)
-
properties是用来加载外部配置文件的,其写法如下:
<!--使用resource加载外部配置文件--> <properties resource="外部配置文件的路径"> <!--如果外部配置文件中有该属性,那么内部定义的属性被外部属性覆盖--> <property name="" value=""/> </properties>
-
MyBatis加载属性的顺序
先读取properties元素体内自定义的属性,再读取properties中resource指定路径的外部配置文件中的属性,如果在外部配置文件中有同名属性,则后者会覆盖前者。
-
-
typeAliases(类型别名)
有时候写实体类的全路径比较麻烦,所以可以取一个别名,这样可以在Mapper.xml中直接写别名调用。
-
定义单个别名
定义单个别名的配置写法如下:
<typeAliases> <!--定义单个别名 type:需要被取别名的对象全路径 alias:对应的别名 --> <typeAlias type="com.xurenyi.pojo.User" alias="user"></typeAlias> </typeAliases>
-
批量定义别名
批量定义别名的配置写法如下:
<typeAliases> <!--批量定义别名 会扫描整个包下的类,别名是类名(大小写不敏感) --> <package name="com.xurenyi.pojo"></package>
-
-
mappers(映射器)
-
用法1
<mapper resource="mapper.xml配置文件的全路径"></mapper>
-
用法2
<mapper class="对应的mapper类的路径"></mapper>
此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
-
用法3
<package name="包路径"></mapper>
此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
实际项目开发中,会使用这种方法。
-