MyBatis基础
MyBatis
MyBatis 是 java 平台下一款优秀的持久层框架, 它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object, 简单普通的 Java 对象) 映射成数据库中的记录。
其前身为 apache 的 ibatis 后来迁移到 Gihub 并更名为 MyBatis
特点:
1. 轻量级自身不依赖其他任何 JAR, 但需要提供 JDBC 实现
2. 灵活, 更加适用于需求变化频繁的互联网应用
3. 学习成本低, 相比 ORM 框架而言, 掌握 MyBatis 的使用是很轻松的
在结构中的位置
可以看出 MyBatis 处在 DAO(数据访问对象) 的位置, 回顾一下 DAO 的工作职责:
连接数据库
接收输入数据
拼接并执行 SQL
解析并返回结果
为什么需要 MyBatis
使用 JDBC 完成 DAO 层存在以下问题
-
每次操作都需要手动的创建连接, 最后关闭连接
对于重复代码通常开发者都会进行封装, 但是由于每个人的编码风格不同导致封装的代码也没有固定的套路
MyBatis 将数据库连接相关的参数放到配置 XML 中并封装了创建连接的代码
-
频繁的创建和销毁连接
由于数据库连接使用的是 TCP 长连接, 并发量大的系统中, 这样的方式会导致数据库连接资源耗尽
MyBatis 本身实现了连接池, 可以解决这一问题, 当然后续会更换其他更好的连接池
-
接受参数拼接 SQL 语句并执行
每一条 SQL 语句都是直接写在代码中 (硬编码), 如果后期需求发生变化, 则需要修改源码中的 SQL, 然后重新编译, 测试.....
MyBatis 将 SQL 语句从代码中剥离到 Mapper.xml 映射文件中
-
解析结果
JDBC 返回的是 ResultSet, 必须手动将其映射到一个个的对象中, 同样是重复度很高的代码; 并且存在硬编码问题
MyBatis 实现了入参映射到 SQL 参数, 以及结果集映射到 POJO 对象
更多功能
MyBatis 在解决上述问题的同时提供了更多实用的功能
-
动态 SQL, 即在 SQL 语句中可以包含逻辑处理 (判断, 循环等....)
-
高级映射, 支持一对一, 一对多映射
-
动态代理 Mapper, 使得可以用面向对象的方式来完成数据库操作
-
逆向工程, 根据表结构自动生成,POJO,Mapper 映射和 Mapper 接口, 包含简单的 CRUD
MyBatis 构架
-
SqlMapConfig.xml 作为全局配置, 指定 MyBatis 的基本参数, 如运行环境 (开发, 发布), 事务管理器, 数据来源等; 以及需要加载的 mapper 映射文件 (从源码中剥离出来的 SQL 语句)
-
SqlSessionFactory, 负责读取 SqlMapConfig 中的参数创建会话
-
SqlSession, 通过 SqlSessionFactory 获取一个 Session(会话)
-
Executor 真正负责执行 sql 语句的对象
-
MappedStatement 用于将输入参数映射到 sql 语句, 以及结果集映射到 POJO
上述构架中,SqlSession 以下的部分是 MyBatis 封装好的,SqlSession 负责调用它们完成操作; 开发过程中不需要涉及 (特殊需求除外);
另外 SqlSessionFactory 和 SqlSession 也可以通过简单的代码获取到, 后续 Spring 框架能够自动创建它们
所以使用 MyBatis 的重点就落在了SqlMapConfig.xml 以及 Mapper.xml中
CRUD 入门
环境搭建
官方文档:https://mybatis.org/mybatis-3/getting-started.html
数据库脚本: 链接: https://pan.baidu.com/s/1UCOKluLqqkZ8PNMoie_0vw 提取码: h43m
-
创建项目
这里采用 Maven 来引入 MyBatis, 项目采用普通的 Java 项目, 不使用 Maven 骨架
-
添加依赖
<properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.17</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
-
提供配置文件
-
MyBatis 全局配置
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <!-- 可配置多个环境 并指定默认使用的环境 --> <environment id="development"> <!-- 指定事务管理器 --> <transactionManager type="JDBC"/> <!-- 指定数据源 就是数据来自哪里 这里默认使用 MyBatis 自带的连接池 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <!-- // 表示本机 localhost & 就是 & xml 中 & 需要转译 --> <property name="url" value="jdbc:mysql:///mybatisDB?serverTimezone=Asia/Shanghai&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="admin"/> </dataSource> </environment> </environments> <!-- 指定要加载的映射文件 --> <mappers> <mapper resource="mapper/ProductsMapper.xml"/> </mappers> </configuration>
-
log4 日志模块, 定义输出格式的配置
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
-
查询数据
查询单个
- 创建并编写 Mapper
ProductsMapper.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 用于多个 Mapper 出现相同的 sql 时区分不同包 -->
<mapper namespace="com.yyh.UserMapper">
<span class="hljs-comment"><!--查询语句
id用于表示这条sql
parameterType 表示 sql语句接受一个整数参数
resultType表示 将结果映射到Products对象中
#{} 表示一个站位符等同于 ?
若输入参数是基础数据类型则可以随意写
若输入是一个POJO则写属性名称--></span>
<span class="hljs-tag"><<span class="hljs-name">select</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"selectProductBtId"</span> <span class="hljs-attr">parameterType</span>=<span class="hljs-string">"int"</span> <span class="hljs-attr">resultType</span>=<span class="hljs-string">"com.yyh.pojo.Products"</span>></span>
select *from products where pid = #{pid}
<span class="hljs-tag"></<span class="hljs-name">select</span>></span>
</mapper>
不要忘记将这个 mapper 配置到 mybatis-config.xml 中
<mappers>
<mapper resource="mapper/ProductsMapper.xml"/>
</mappers>
- 执行测试
@Test
public void TestSelect() throws IOException {
// 获取的工厂构造器
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 加载配置文件
InputStream stream = Resources.getResourceAsStream("mybatis-config.xml");
// 获得会话工厂
SqlSessionFactory factory = builder.build(stream);
// 获得会话
SqlSession sqlSession = factory.openSession();
<span class="hljs-comment">//执行sql</span>
<span class="hljs-type">Products</span> <span class="hljs-variable">product</span> <span class="hljs-operator">=</span> sqlSession.selectOne(<span class="hljs-string">"selectProductBtId"</span>, <span class="hljs-number">1</span>);
System.out.println(product);
}
查询多个
需求: 使用模糊查询名字中带有新疆的数据
-
修改 mapper 增加标签
<select id="selectProductLikeName" parameterType="string" resultType="com.yyh.pojo.Products"> select *from products where pname like "%${name}%" </select>
-
执行测试
@Test public void selectTest2() throws IOException { // 获得会话 SqlSession sqlSession = factory.openSession(); // 执行 sql List<Products> products = sqlSession.selectList("selectProductLikeName", "新疆"); System.out.println(products); sqlSession.close(); }
由于多个测试方法都需要工厂所以讲工厂作为属性并在,@Before 中进行初始化
public class MyBatisTest { private SqlSessionFactory factory; @Before public void init() throws IOException { // 获取的工厂构造器 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); // 加载配置文件 InputStream stream = Resources.getResourceAsStream("mybatis-config.xml"); // 获得会话工厂 factory = builder.build(stream); } ........}
需要注意的是: 在 mapper 中当需要做将参数进行字符串拼接时则不能在使用 #{} 需更换为 ${} 表示将参数结果提取并拼接到字符串中
注意了: 若 MyBatis 版本低于 3.5.2 则在字符串中使用 #{xxx} 的方式将导致找不到属性异常, 在低版本中 MyBatis 将 #{xxx} 中的 xxx 当做参数的属性去参数中查找 get 方法 (很明显找不到), 在字符串内和字符串外获取属性的行为竟然不同,,,,, 好在 3.5.2 解决了这个问题
插入数据
-
修改 mapper 增加标签
<!-- insert 没有返回值 --> <insert id="insertProduct" parameterType="com.yyh.pojo.Products"> insert into products values(null,#{pname},#{price},#{pdate},#{cid}) </insert>
-
执行测试
@Test public void insertTest() { // 设置自动提交为 true 默认为 false SqlSession session = factory.openSession(true); // 实例化一个商品 Products p = new Products(); p.setPname("虎邦辣酱"); p.setPrice(5.5f); p.setPdate(new Date()); p.setCid("s001"); session.insert("insertProduct",p); //session.commit();// 手动 commit session.close(); }
强调:
当 sql 为 update insert delete 时需要 commit 才会生效,SqlSession 默认不自动提交的, 可以在获取 SqlSession 时指定自动提交, 也可以在执行完后手动调用 commit;
sql 为 update insert delete 时返回值为受影响行数
-
获取插入记录的 id
很多情况下需要获取刚刚添加的记录的 id 用来做表关联, 要获得 id 有两种方式
3.1 对于支持自增的数据库 MySQL
<insert id="insertProduct" parameterType="com.yyh.pojo.Products" keyProperty="pid" useGeneratedKeys="true"> insert into products values(null,#{pname},#{price},#{pdate},#{cid}) </insert> MyBatis会把id存储到传入对象的pid属性中
3.2 兼容不支持自增的数据库
<!-- 插入数据 --> <insert id="insertProduct" parameterType="com.yyh.pojo.Products" > insert into products values(null,#{pname},#{price},#{pdate},#{cid}) <!-- 指定如何获取 id 并放入对象某个属性中 --> <selectKey resultType="int" keyProperty="pid" order="AFTER" > select last_insert_id(); </selectKey> </insert>
注意:select last_insert_id(); 是 mysql 的函数,oracle 没有, 那就需要将其替换为 oracle 中生产 id 的函数,selectKey 的原理是执行这个 sql 函数, 然后将结果放入对象的属性中,order 指定 id 放入对象属性是在执行 sql 前或者后
关于 before 和 after 的选择, 要根据 id 的来源是自增的还是自己编写语句获取的, 如下:
更新数据
-
修改 mapper 增加标签
<!-- 更新数据 --> <update id="updateProduct" parameterType="com.yyh.pojo.Products"> update products set pname = #{pname}, price = #{price}, pdate = #{pdate}, cid = #{cid} where pid = #{pid} </update>
-
执行测试
@Test public void updateTest() { SqlSession session = factory.openSession(); // 先获取一条记录 Products p = session.selectOne("selectProductBtId", 1); // 更新属性 p.setPrice(99.99f); // 执行更新 int count = session.update("updateProduct", p); System.out.println("update count :"+count); // 提交事务 session.commit(); session.close(); }
删除数据
-
修改 mapper 增加标签
<!-- 通过 id 删除 --> <delete id="deleteProductById" parameterType="int"> delete from products where pid = #{pid} </delete>
-
执行测试
@Test public void deleteTest() { SqlSession session = factory.openSession(); int count = session.delete("deleteProductById", 1); System.out.println("delete count :"+count); session.commit(); session.close(); }