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
解析并返回结果
使用JDBC完成DAO层存在以下问题
每次操作都需要手动的创建连接,最后关闭连接
对于重复代码通常开发者都会进行封装,但是由于每个人的编码风格不同导致封装的代码也没有固定的套路
频繁的创建和销毁连接
由于数据库连接使用的是TCP长连接,并发量大的系统中,这样的方式会导致数据库连接资源耗尽
接受参数拼接SQL语句并执行
每一条SQL语句都是直接写在代码中(硬编码),如果后期需求发生变化,则需要修改源码中的SQL,然后重新编译,测试.....
解析结果
JDBC返回的是ResultSet,必须手动将其映射到一个个的对象中,同样是重复度很高的代码;并且存在硬编码问题
MyBatis在解决上述问题的同时提供了更多实用的功能
动态SQL,即在SQL语句中可以包含逻辑处理(判断,循环等....)
高级映射,支持一对一,一对多映射
动态代理Mapper,使得可以用面向对象的方式来完成数据库操作
逆向工程,根据表结构自动生成,POJO,Mapper映射和Mapper接口,包含简单的CRUD
SqlMapConfig.xml作为全局配置,指定MyBatis的基本参数,如运行环境(开发,发布),事务管理器,数据来源等; 以及需要加载的mapper映射文件(从源码中剥离出来的SQL语句)
SqlSessionFactory,负责读取SqlMapConfig中的参数创建会话
MappedStatement用于将输入参数映射到sql语句,以及结果集映射到POJO
上述构架中,SqlSession以下的部分是MyBatis封装好的,SqlSession负责调用它们完成操作; 开发过程中不需要涉及(特殊需求除外);
另外SqlSessionFactory和SqlSession也可以通过简单的代码获取到,后续Spring框架能够自动创建它们
所以使用MyBatis的重点就落在了 SqlMapConfig.xml以及Mapper.xml 中
官方文档:https://mybatis.org/mybatis-3/getting-started.html
创建项目
这里采用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
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 用于多个Mapper出现相同的sql时区分不同包--> <mapper namespace="com.kkb.UserMapper"> <!--查询语句 id用于表示这条sql parameterType 表示 sql语句接受一个整数参数 resultType表示 将结果映射到Products对象中 #{} 表示一个站位符等同于 ? 若输入参数是基础数据类型则可以随意写 若输入是一个POJO则写属性名称--> <select id="selectProductBtId" parameterType="int" resultType="com.kkb.pojo.Products"> select *from products where pid = #{pid} </select> </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(); //执行sql Products product = sqlSession.selectOne("selectProductBtId", 1); System.out.println(product); }
需求:使用模糊查询名字中带有新疆的数据
修改mapper增加标签
<select id="selectProductLikeName" parameterType="string" resultType="com.kkb.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解决了这个问题,真TM坑......
修改mapper增加标签
<!-- insert没有返回值--> <insert id="insertProduct" parameterType="com.kkb.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.kkb.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.kkb.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.kkb.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(); }