一、Mybatis(初识)
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
一、简介
- apache的一个开源项目ibatis, 2010年这个项目由apache 迁移到了google code,并且改名为MyBatis 。
- 2013年11月迁移到Github .
- Mybatis官方文档 : http://www.mybatis.org/mybatis-3/zh/index.html
- GitHub : https://github.com/mybatis/mybatis-3
二、优势
-
一个半自动化的ORM框架 (Object Relationship Mapping) -->对象关系映射的持久层框架
-
简单易学:
本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件就可以了,易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
-
代码灵活性:
mybatis不会对应用程序或者数据库的现有设计强加任何影响。sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
-
解除sql与程序代码的耦合:
通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
-
提供xml标签,支持编写动态sql
二、入门
项目搭建 》 mybatis配置文件 》 代码编写与测试
一、项目搭建
1、搭建实验数据库
CREATE DATABASE `mybatis`;
USE `mybatis`;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(20) NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `user`(`id`,`name`,`pwd`) values (1,'hello','123456'),(2,'world','123456');
2、导入MyBatis相关 jar 包
<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>5.1.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
3、Maven静态资源配置
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
二、Mybatis配置文件
编写MyBatis核心配置文件
- 查看帮助文档
<?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"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai"></property>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="zak/plus/dao/UserDao.xml"/>
</mappers>
</configuration>
三、代码编写与测试
1、实体类User
@Data
public class User {
private int id;
private String name;
private String pwd;
}
2、Mapper接口
public interface UserDao{
List<User> findAll();
}
3、Mapper接口对应的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="zak.plus.dao.UserDao">
<select id="findAll" resultType="zak.plus.entity.User">
select * from user;
</select>
</mapper>
4、测试
public class Demo {
@Test
public void test(){
SqlSession session = MybatisUtils.getSession();
//方法一:
List<User> users = session.selectList("zak.plus.dao.UserMapper.findAll");
//方法二:
// UserMapper mapper = session.getMapper(UserMapper.class);
// List<User> users = mapper.findAll();
for (User user: users){
System.out.println(user);
}
session.close();
}
}
三、进阶
一、多对一与一对多
对于数据库表中常见的关系一对多和多对一映射,而mybatis的结果集映射正是对应着这些关系并且还支持强大的自定义关系映射来满足使用者的自定义需求。
假设有两张表的关系如图示:多名学生有一名老师、一名老师有多名学生
表创建
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
1、多对一实现
一、使用查询嵌套实现
<!--
需求:获取所有学生及对应老师的信息
思路:
1. 获取所有学生的信息
2. 根据获取的学生信息的老师ID->获取该老师的信息
3. 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般使用关联查询?
1. 做一个结果集映射:StudentTeacher
2. StudentTeacher结果集的类型为 Student
3. 学生中老师的属性为teacher,对应数据库中为tid。
多个 [1,...)学生关联一个老师=> 一对一,一对多
4. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查询
-->
<resultMap id="StudentTeachers" type="Student">
<!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getStudents" resultMap="StudentTeachers">
select * from student
</select>
二、按结果嵌套实现(推荐写法)
<resultMap id="StudentTeacherList" type="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<!--关联对象property 关联对象在Student实体类中的属性-->
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
<!--按查询结果嵌套处理思路:1. 直接查询出结果,进行结果集的映射-->
<select id="getStudentList" resultMap="StudentTeacherList">
select s.id sid, s.name sname, t.name tname
from student s left join teacher t on s.tid = t.id
</select>
2、一对多实现
一、使用查询嵌套实现
<!--
思路:
1. 从学生表和老师表中查出学生id,学生姓名,老师姓名
2. 对查询出来的操作做结果集映射
1. 集合的话,使用collection!
JavaType和ofType都是用来指定对象类型的
JavaType是用来指定pojo中属性的类型
ofType指定的是映射到list集合属性中pojo的类型。
-->
<resultMap id="TeacherStudents" type="Teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--column是一对多的外键 , 写的是一的主键的列名-->
<collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentsByTeacherId"/>
</resultMap>
<select id="getTeachers" resultMap="TeacherStudents">
select * from teacher
</select>
<select id="getStudentsByTeacherId" resultType="Student">
select * from student where tid = #{id}
</select>
二、按结果嵌套实现(推荐写法)
<resultMap id="TeacherStudentList" type="Teacher">
<result property="id" column="id"/>
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid" />
<result property="name" column="sname" />
<result property="tid" column="tid" />
</collection>
</resultMap>
<select id="getTeacherList" resultMap="TeacherStudentList">
select s.id sid, s.name sname , t.name tname, t.id id, t.id tid
from teacher t left join student s on s.tid = t.id
</select>
二、动态SQL
官网描述:
- MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
- 虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。
- 动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。
if
、choose
、trim
、foreach
- if:判断
<select id="findName" resultType="zak.plus.entity.User">
select * from user
<where>
<if test="name != null and name != ''">
name like concat('%',#{name},'%')
</if>
</where>
limit 1
</select>
注意点:mybatis中空串与0是比较相同的,mybatis底层空字符串是通过length比较
-
trim:格式化 (where, set)
-
where
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除
<select id="findNameOrPwd" resultType="zak.plus.entity.User"> select * from user <where> <if test="name != null "> name = #{name} </if> <if test="pwd != null"> and pwd = #{pwd} </if> </where> limit 1 </select>
-
set
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)
<update id="updateUser" parameterType="User"> update user <set> <if test="name != null"> name = #{name}, </if> <if test="pwd != null"> pwd = #{pwd}, </if> </set> where id = #{id} </update>
-
trim
等价于where、set,还可以自定义格式
- < trim prefix="" prefixOverrides="" suffix="" suffixOverrides="" >
- 移除所有标签中语句前面 prefixOverrides 属性中指定的内容,并且在前面插入 prefix 属性中指定的内容
- 移除所有标签中语句后面 suffixOverrides 属性中指定的内容,并且在后面插入 suffix 属性中指定的内容
- 这四个属性可以任意搭配
<trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim> <trim prefix="SET" suffixOverrides=","> ... </trim>
-
-
choose:选择 (when, otherwise)
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句
<select id="findNameOrPwd" resultType="zak.plus.entity.User"> select * from user <!--<where>--> <!--<if test="name != null ">--> <!--name = #{name}--> <!--</if>--> <!--<if test="pwd != null">--> <!--and pwd = #{pwd}--> <!--</if>--> <!--</where>--> <where> <choose> <when test="name != null and name !=''"> name = #{name} </when> <when test="pwd != null and pwd !=''"> and pwd = #{pwd} </when> <otherwise> and 1 = 1 </otherwise> </choose> </where> limit 1 </select>
-
foreach:遍历
- foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量
- 也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符
- 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach
- 当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值
<select id="findIds" resultType="User">
select * from user
<!-- select * from user where id in (1,2) -->
<where>
id in
<foreach collection="ids" item="id" open=" (" close=")" separator=",">
#{id}
</foreach>
</where>
</select>
小知识点**:
一、Mybatis中的作用域
1、 SqlSessionFactoryBuilder : 最佳作用域是方法作用域
作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在.
2、SqlSessionFactory :最佳作用域是应用作用域
- SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。
- 由于 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。
3、SqlSession :最佳的作用域是请求或方法作用域
如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭
二、持久化
持久化是将程序数据在持久状态和瞬时状态间转换的机制
- 即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等。
- JDBC就是一种持久化机制。文件IO也是一种持久化机制。
为什么需要持久化服务呢?那是由于内存本身的缺陷引起的
- 内存断电后数据会丢失,但有一些对象是无论如何都不能丢失的,比如银行账号等,遗憾的是,人们还无法保证内存永不掉电。
- 内存过于昂贵,与硬盘、光盘等外存相比,内存的价格要高2~3个数量级,而且维持成本也高,至少需要一直供电吧。所以即使对象不需要永久保存,也会因为内存的容量限制不能一直呆在内存中,需要持久化来缓存到外存。