一、简介 1. MyBatis是什么 MyBatis的前身叫iBatis
是一个持久层框架,或称为 ORM框架
用来访问数据库,做数据持久化操作
本质上只是对JDBC进行封装,简化JDBC繁琐的操作
注:框架就是别人写好的,对某些技术进行的封装,封装成对应的jar、js、css等,我们可以直接拿过来使用,简化开发
2. 持久层 DAO:Data Access Object 数据访问对象
用来对数据进行持久化操作,如将数据存入数据库、硬盘等,可以永久保存
3. ORM Object Relational Mapping 对象关系映射
Java程序和数据库之间的映射关系:
类 ——> 表
对象 ——> 一条数据
属性 ——> 列
4. 回顾JDBC JDBC访问数据库的步骤
1 2 3 4 5 6 7 8 9 10 11 Class.forName(driverClassName); Connection conn = DriverManager.getConnection(url,user,password);PrepareStatement ps = conn.preparedStatement(sql);ResultSet rs = ps.executeQuery();while (rs.next){ } rs.close(); ps.close(); conn.close();
数据库操作中的可变部分:
二、第一个MyBatis程序 1. 创建项目并添加依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.13</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.46</version > <scope > runtime</scope > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.24</version > <scope > provided</scope > </dependency >
2. 数据库设计 1 2 3 4 5 6 7 8 9 10 11 drop database if exists mybatis;create database mybatis charset utf8;use mybatis; create table t_user( id int primary key auto_increment comment '编号' , username varchar (20 ) unique not null comment '用户名' , password varchar (50 ) comment '密码' , phone varchar (20 ) comment '电话' , address varchar (100 ) comment '地址' )engine innodb default charset utf8 comment '用户表' ;
3. 创建主配置文件config 主配置文件,在一个mybatis工程中有且只有一个
用来配置与整个工程相关的信息,如环境配置、别名配置、插件配置、注册mapper文件等
文件名可自定义,一般命名为mybatis-config.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <?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 ="hello" > <environment id ="hello" > <transactionManager type ="jdbc" > </transactionManager > <dataSource type ="POOLED" > <property name ="driver" value ="com.mysql.jdbc.Driver" > </property > <property name ="url" value ="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true& characterEncoding=utf-8" > </property > <property name ="username" value ="root" > </property > <property name ="password" value ="root" > </property > </dataSource > </environment > </environments > <mappers > <mapper resource ="mapper/UserMapper.xml" /> </mappers > </configuration >
小技巧:
创建配置文件的模板:Settings——>搜索template——>File and Code Templates——>Files——>Create Template
4. 创建映射文件mapper 映射配置文件,在一个mybatis工程中可以有多个mapper文件
用来配置dao功能相关的sql操作,如sql语句、CURD操作、字段映射等
每个实体类对应一个映射文件,每一个mapper文件相当于原来三层架构中dao实现类
文件名可自定义,一般命名为XxxMapper.xml,放到mapper文件夹中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?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 ="dao.UserDao" > <insert id ="insertUser" parameterType ="entity.User" > insert into t_user (username, password, phone, address) values (#{username},#{password},#{phone},#{address}) </insert > </mapper >
小技巧:
去除背景:Settings——>Editor——>Color Scheme——>General——>Code——>Injected language fragment,取消勾选右边的Background
XML注释风格:Settings——>Editor——>Code Style——>XML——>Code Generation,取消勾选下面的两个
编写SQL时提示表和列:
在右侧的工具栏,添加数据库连接
Settings——>搜索SQL Dialects——>将右边的都选择为MySQL
5. 测试类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public static void main (String[] args) { SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder (); SqlSessionFactory factory = builder.build(Test01.class.getClassLoader().getResourceAsStream("mybatis-config.xml" )); SqlSession session = factory.openSession(); User user = new User (); user.setUsername("alice" ); user.setPassword("123" ); user.setPhone("110" ); user.setAddress("南京" ); UserDao userDao = session.getMapper(UserDao.class); userDao.insertUser(user); session.commit(); }
6. MyBatisUtil工具类 MyBatisUtil工具类:
(1)MyBatisUtils主要职责是:● 帮助我们初始化SqlSessionFactory这个对象;同时让SqlSessionFactory全局唯一;● 获得SqlSession对象的方法;● 关闭SqlSession对象的方法;
(2)在实际开发中,会经常使用MyBatisUtils工具类;
添加模板,实现快捷操作:Settings——>搜索template——>Live Templates
1 2 3 4 5 6 7 8 9 10 11 12 13 SqlSession session = null ;try { session = MyBatisUtil.getSession(); $END$ session.commit(); } catch (Exception e) { e.printStackTrace(); session.rollback(); } finally { MyBatisUtil.close(); }
三、config文件 1. settings 1 2 3 4 5 6 <settings > <setting name ="logImpl" value ="STDOUT_LOGGING" /> </settings >
logImpl可以输出日志 注:setting一定要在environment的上面,否则会报错。
2. typeAliases 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <typeAliases > <package name ="entity" /> </typeAliases >
3. properties 1 2 3 4 <properties resource ="datasource.properties" > </properties >
1 2 3 4 5 6 7 <property name ="driver" value ="${jdbc.driver}" > </property > <property name ="url" value ="${jdbc.url}" > </property > <property name ="username" value ="${jdbc.username}" > </property > <property name ="password" value ="${jdbc.password}" > </property >
四、mapper文件 1. insert 保存返回主键
1 2 3 4 5 6 7 8 9 10 11 12 <insert id ="insert" parameterType ="User" useGeneratedKeys ="true" keyProperty ="id" > insert into t_user (username, password, phone, address) values (#{username},#{password},#{phone},#{address}) </insert >
2. update 1 2 3 4 5 6 7 8 9 10 11 <update id ="updateUser" parameterType ="User" > update t_user set username=#{username}, password=#{password}, phone=#{phone}, address=#{address} where id=#{id} </update >
3. delete 1 2 3 4 5 6 7 <delete id ="deleteById" parameterType ="int" > delete from t_user where id=#{id} </delete >
4. select 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <select id ="selectById" parameterType ="int" resultType ="User" > select id,username,password,phone,address from t_user where id=#{id} </select > <select id ="selectAll" resultType ="User" > select <include refid ="userColumn" /> from t_user </select > <select id ="selectByUsername" resultType ="User" > select <include refid ="userColumn" /> from t_user where username like #{username} </select >
5. sql片段 1 2 3 4 5 6 7 8 9 10 11 12 <sql id ="UserColumn" > id,username,password,phone,address </sql > <sql id ="BaseQuery" > select id,username,password,phone,address from t_user </sql >
五、手动映射 当数据库查询结果的字段名与Java对象的属性名不同时,如何映射?
1 2 3 4 5 6 7 create table t_user2( user_id int primary key auto_increment comment '编号' , user_username varchar (20 ) unique not null comment '用户名' , user_password varchar (50 ) comment '密码' , user_phone varchar (20 ) comment '电话' , user_address varchar (100 ) comment '地址' )engine innodb default charset utf8 comment '用户表' ;
1. 使用别名 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <select id ="selectById2" parameterType ="int" resultType ="User" > select user_id id, user_username username, user_password password , user_phone phone , user_address address from t_user2 where user_id=#{id} </select >
2. 使用resultMap 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <resultMap id ="UserMap" type ="User" > <id property ="id" column ="user_id" /> <result property ="username" column ="user_username" /> <result property ="password" column ="user_password" /> <result property ="phone" column ="user_phone" /> <result property ="address" column ="user_address" /> </resultMap >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select id ="selectById3" parameterType ="int" resultMap ="UserMap" > select user_id, user_username, user_password , user_phone, user_address from t_user2 where user_id=#{id} </select >
六、多个参数 1. 使用@Param()注解 1 2 public User selectByUsernameAndPassword (@Param("username") String username, @Param("password") String password) ;
1 2 3 4 5 6 7 <select id ="selectByUsernameAndPassword" resultType ="User" > <include refid ="BaseQuery" /> where username=#{username} and password=#{pwd} </select >
注:如果方法只有一个参数,不需要加@Param注解,可以在占位符#{ }中使用任意名称
2. 将参数封装为对象 1 2 3 4 5 <select id ="selectByUsernameAndPassword2" parameterType ="UserDTO" resultType ="User" > <include refid ="BaseQuery" /> where username=#{username} and password=#{password} </select >
注:可以自定义一个参数对象,如UserDTO、UserParam等
3. 将参数封装为Map 1 2 3 4 5 6 7 <select id ="selectByUsernameAndPassword3" resultType ="User" > <include refid ="BaseQuery" /> where username=#{username} and password=#{password} </select >
注:parameterType属性可以省略
4. #{}与${}的区别 #{ }的含义
#{ }表示一个占位符 即JDBC中的?
占位不会出现SQL注入的问题
${ }的含义
${}表示一个拼接符
拼接会导致SQL注入,如查询条件输入'' or 1=1就会有注入情况
如果排序时要指定排序列名和排序方式,此时就必须使用字符的拼接,即使用${ }
七、动态SQL 根据条件的不同,动态的拼接SQL语句,称为动态SQL
传统JDBC拼接:
1 2 3 4 5 6 7 8 StringBuffer sql = new StringBuffer(); sql.append("select * from t_user where 1=1"); if(username!= null && ! "".equals (username)){ sql.append(" and username=? "); } if(password!= null && ! "".equals (password)){ sql.append(" and password=? "); }
1. if 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <select id ="selectByParams" parameterType ="User" resultType ="User" > <include refid ="BaseQuery" /> where 1=1 <if test ="username != null and username != ''" > and username = #{username} </if > <if test ="password != null and password != ''" > and password = #{password} </if > <if test ="phone != null and phone != ''" > and phone = #{phone} </if > <if test ="address != null and address != ''" > and address = #{address} </if > </select >
2. choose 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <select id ="selectByParams2" parameterType ="User" resultType ="User" > <include refid ="BaseQuery" /> where <choose > <when test ="username != null and username != ''" > username = #{username} </when > <when test ="password != null and password != ''" > password = #{password} </when > <when test ="phone != null and phone != ''" > phone = #{phone} </when > <when test ="address != null and address != ''" > address = #{address} </when > <otherwise > 1=1 </otherwise > </choose > </select >
3. where 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <select id ="selectByParams3" parameterType ="User" resultType ="User" > <include refid ="BaseQuery" /> <where > <if test ="username != null and username != ''" > and username = #{username} </if > <if test ="password != null and password != ''" > or password = #{password} </if > <if test ="phone != null and phone != ''" > and phone = #{phone} </if > <if test ="address != null and address != ''" > and address = #{address} </if > </where > </select >
4. set 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <update id ="updateUser2" parameterType ="User" > update t_user <set > <if test ="username != null and username != ''" > username = #{username}, </if > <if test ="password != null and password != ''" > password = #{password}, </if > <if test ="phone != null and phone != ''" > phone = #{phone}, </if > <if test ="address != null and address != ''" > address = #{address}, </if > </set > where id = #{id} </update >
5. trim 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <insert id ="insertUser2" parameterType ="User" > insert into t_user <trim prefix ="(" suffix =")" suffixOverrides ="," > <if test ="id != null" > id,</if > <if test ="username != null and username != ''" > username,</if > <if test ="password != null and password != ''" > password,</if > <if test ="phone != null and phone != ''" > phone,</if > <if test ="address != null and address != ''" > address,</if > </trim > values <trim prefix ="(" suffix =")" suffixOverrides ="," > <if test ="id != null" > #{id},</if > <if test ="username != null and username != ''" > #{username},</if > <if test ="password != null and password != ''" > #{password},</if > <if test ="phone != null and phone != ''" > #{phone},</if > <if test ="address != null and address != ''" > #{address},</if > </trim > </insert >
6. foreach 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <select id ="selectByIds" resultType ="User" > <include refid ="BaseQuery" /> where id in <foreach collection ="list" item ="id" open ="(" close =")" separator ="," > #{id} </foreach > </select >
八、多表关系映射 1. 简介 关联关系:
多对一,多个员工都在同一个部门中
一对多,一个部门中有多个员工
一对一,一个员工只能有一个身份证
多对多,一个员工可以同时开发多个项目,一个项目也可以同时有多个员工开发
数据库设计:
1 2 3 4 5 6 7 8 9 10 11 12 create table t_dept( id int primary key auto_increment comment '编号' , name varchar (20 ) comment '部门名称' )engine innodb default charset utf8 comment '部门表' ; create table t_emp( id int primary key auto_increment comment '编号' , name varchar (20 ) comment '姓名' , salary double comment '工资' , dept_id int comment '部门编号' , foreign key (dept_id) references t_dept(id) )engine innodb default charset utf8 comment '员工表' ;
2. 保存操作 1 2 3 4 5 6 7 8 <mapper namespace ="dao.DeptDao" > <insert id ="insertDept" parameterType ="Dept" useGeneratedKeys ="true" keyProperty ="id" > insert into t_dept (name) values (#{name}) </insert > </mapper >
1 2 3 4 5 6 7 8 <mapper namespace ="dao.EmpDao" > <insert id ="insertEmp" parameterType ="Emp" > insert into t_emp (name, salary, dept_id) values (#{name},#{salary},#{dept.id}) </insert > </mapper >
3. 多对一 在一个对象中定义另一个对象的属性
两种实现方式:
使用关联属性,即直接使用association标签
使用嵌套查询,即使用association的select属性,引用其他select,通过多个单表查询来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <resultMap id ="BaseMap" type ="Emp" > <id property ="id" column ="id" /> <result property ="name" column ="name" /> <result property ="salary" column ="salary" /> </resultMap > <resultMap id ="EmpMap" type ="Emp" extends ="BaseMap" > <association property ="dept" javaType ="Dept" > <id property ="id" column ="deptId" /> <result property ="name" column ="deptName" /> </association > </resultMap > <resultMap id ="EmpMap2" type ="Emp" extends ="BaseMap" > <association property ="dept" javaType ="Dept" select ="dao.DeptDao.selectById" column ="dept_id" > </association > </resultMap > <select id ="selectAll" resultMap ="EmpMap" > select <include refid ="EmpColumn" /> from t_emp e left join t_dept d on e.dept_id=d.id </select > <select id ="selectAll" resultMap ="EmpMap2" > select id,name,salary,dept_id from t_emp </select >
嵌套查询的缺点:效率低,会进行多次查询,存在N+1问题
首先查询了1次emp表,获取到N条dept_id的记录
对于每一个不同的dep_id,都会去dept表中进行一次查询,可能会查询N次
所以,总查询次数可能为:1次emp表+N次dept表
4. 一对多 在一个对象中定义另一个对象的集合
两种实现方式:
使用集合属性,即直接使用collection标签
使用嵌套查询,即使用collection的select属性,引用其他select,通过多个单表查询来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <resultMap id ="BaseMap" type ="Dept" > <id property ="id" column ="id" /> <result property ="name" column ="name" /> </resultMap > <resultMap id ="DeptMap" type ="Dept" extends ="BaseMap" > <collection property ="emps" ofType ="Emp" > <id property ="id" column ="empId" /> <result property ="name" column ="empName" /> <result property ="salary" column ="salary" /> </collection > </resultMap > <resultMap id ="DeptMap2" type ="Dept" extends ="BaseMap" > <collection property ="emps" ofType ="Emp" select ="dao.EmpDao.selectByDeptId" column ="id" /> </resultMap > <select id ="selectAll" resultMap ="DeptMap" > select <include refid ="DeptColumn" /> from t_dept d left join t_emp e on d.id=e.dept_id </select > <select id ="selectAll" resultMap ="DeptMap2" > select id,name from t_dept </select >
5. 懒加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <resultMap id ="DeptMap2" type ="Dept" extends ="BaseMap" > <collection property ="emps" ofType ="Emp" select ="dao.EmpDao.selectByDeptId" column ="id" fetchType ="lazy" /> </resultMap > <settings > <setting name ="lazyLoadingEnabled" value ="true" /> </settings >
九、缓存 1. 简介 将从数据库中查询出来的数据放入缓存中,下次使用时不必从数据库查询,而是直接从缓存中读取,避免频繁操作数据库,减轻数据库的压力,同时提高系统性能。
合理使用缓存是优化中最常见的操作。
2. 一级缓存 一级缓存是SqlSession级别的,存储在SqlSession中,默认是开启的
一般来说,一个请求中的所有增删改查操作都是在 同一个sqlSession里面的,可以认为每个请求都有自己的一级缓存
如果同一个SqlSession会话中 2个查询中间有一个 insert 、update或delete 语句,那么之前查询的所有缓存都会清空
流程:
用户发起查询请求,查找某条数据,SqlSession 先去缓存中查找,是否有该数据,如果有,读取;
如果没有,从数据库中查询,并将查询到的数据放入一级缓存区域,供下次查找使用。
当SqlSession 执行commit,即增删改操作时会清空缓存。这么做的目的是避免脏读。
生效的条件:
必须使用同一SqlSession,执行同一个查询方法才会有效
同一个SqlSession,如果查询条件不同,则无效
同一个SqlSession,如果两次查询期间执行了任何一次的增删改操作,则无效
3. 二级缓存 二级缓存是 mapper 级别的缓存,多个SqlSession去操作同一个Mapper的sql语句时,多个SqlSession可以共用二级缓存
二级缓存是跨SqlSession的,因此二级缓存的作用范围更大
二级缓存默认是关闭的,需要手动开启
流程:
开启二级缓存后,用户查询时,会先去二级缓存中找,找不到了再去一级缓存中找
一级缓存也没有查询到,则查询数据库
当SqlSession会话提交或者关闭时,一级缓存的数据会刷新到二级缓存中
启用二级缓存:在 XxxMapper.xml 映射文件中,添加:<cache/>
十、分页插件 1. 简介 PageHelper是一款基于mybatis的分页插件
2. 用法 2.1 添加依赖 1 2 3 4 5 <dependency > <groupId > com.github.pagehelper</groupId > <artifactId > pagehelper</artifactId > <version > 5.1.2</version > </dependency >
2.2 插件的配置 在mybatis核心配置文件中配置插件
1 2 3 4 5 6 <plugins > <plugin interceptor ="com.github.pagehelper.PageInterceptor" > <property name ="helperDialect" value ="mysql" /> </plugin > </plugins >
2.3 使用 分为三步:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int pageNum = 3 ;int pageSize = 4 ;PageHelper.startPage(pageNum, pageSize); UserDao userDao = session.getMapper(UserDao.class);List<User> users = userDao.selectAll(); for (User user:users){ System.out.println(user); } PageInfo<User> pageInfo=new PageInfo <>(users); System.out.println(pageInfo); System.out.println("页码:" +pageInfo.getPageNum()); System.out.println("页大小:" +pageInfo.getPageSize()); System.out.println("总页数:" +pageInfo.getPages()); System.out.println("总条数:" +pageInfo.getTotal()); System.out.println("分页数据:" +pageInfo.getList());
十一、其他 1. MyBatisX 一款全免费且强大的 IDEA 插件,支持跳转,自动补全生成 SQL,代码生成。
1 2 3 4 5 6 7 8 9 10 11 create table t_product( pro_id int primary key auto_increment comment '编号' , pro_name varchar (100 ) comment '产品名称' , pro_price decimal (10 ,2 ) comment '产品价格' , pro_number int comment '产品数量' , pro_introduce text comment '产品介绍' , pro_state tinyint comment '产品状态(0未通过,1审核中,2已审核)' , add_time datetime comment '添加时间' , is_del tinyint comment '是否删除(0正常,1删除)' ) engine innodb default charset utf8 comment '产品表' ;
1 2 3 4 <settings > <setting name ="mapUnderscoreToCamelCase" value ="true" /> </settings >
2. MyBatisCodeHelperPro 功能更强大,收费!