【MyBatis】
第一章 MyBatis 简介
1.1 MyBatis
-
由来:
- MyBaits本是apache的一个开源项目iBatis,改名为MyBatis,实质上Mybatis对iBatis进行了一些改进。
-
简述:
-
MyBatis是一个优秀的持久层框架,对jdbc的操作数据库的过程进行封装,使得开发者只需要关注sql本身,而不需要花费精力去处理例如加载驱动,创建connection、创建statement、手动设置参数,结果集检索等jdbc繁琐的过程代码。
注:对jdbc的封装框架:Hibernate、dbutils、jdbcTemplate(spring自带)
-
-
实现对JDBC的封装原理:
- MyBatis通过xml或者注解的方式将要执行的的各种statement(statement、 preparedStatement、CallableStatement)配置起来,并通过Java对象和statement中的sql进行映射生成最终执行的sql语句,最后由MyBatis框架执行sql并将结果影射成Java对象并返回。
1.2 JDBC的程序代码
1.3 MyBatis的框架核心
- mybatis配置文件:
- 包括
mybatis全局配置文件和mybatis映射文件
,其中: - 全局配置文件:配置了数据源、事务等信息;
- 映射文件:配置了sql执行相关信息;
- 包括
- mybatis通过读取配置文件信息(全局配置文件和映射文件),构造出
SqlSessionFactory
,即会话工厂。 - 通过
SqlSessionFactory
,可以创建SqlSession(会话)
,MyBatis通过SqlSession来操作数据库 - SqlSession本身不能直接操作数据库,通过底层的Executor执行接口来操作数据库,Exceutor接口有两个实现类,一个是普通执行器,一个是缓存执行器(默认)
- Executor执行器要处理的SQL信息是封装到一个底层对象MappedStatement中,该对象包括:sql语句、输入参数映射信息、输出结果集映射信息,其中输入参数和输出结果的映射类型包括:HashMap集合对象、POJO对象类型。
第二章 MyBatis入门
2.1 环境准备
- 创建数据库,建表,导入数据
- 下载MyBatis
- 创建项目
- 导架包(新建lib文件夹将需要的依赖包、核心包、驱动包、junit4测试包导入)
- src文件夹中添加日志文件(新建log4j.properties)
Mybatis使用的日志包是log4j的,所以需要添加log4j.properties,内容可以在mybatis-x-xx.pdf中拷贝
Log4J配置详解
- 配置根Logger,log4j.rootLogger = [ level ] , appenderName
- 【level】是日志的级别,分别有debug -> info -> warn -> error 四种日志级别;
- 【appenderName】,配置日志的输出目录,同一个日志可以配置多个输出目的地;
- 配置log输出目的地:
- org.apache.log4j.ConsoleAppender(控制台)
- org.apache.log4j.FileAppender(文件)
- org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
- org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
- org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
- log信息的格式,无需详细了解
log4j内容:
###Global logging configuration
log4j.rootLogger=ERROR, stdout###Uncomment for MyBatis logging
log4j.logger.org.apache.ibatis=ERROR###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
2.2 开发步骤
-
创建PO类(对应表的model),根据需求创建;
package com.mdy.model; import java.util.Date; public class User { private int id; private String username; //用户名 private String sex; //性别 private Date birthday; //生日 private String address; //地址 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "User [id=" + id + ", username=" + username + ", sex=" + sex + ", birthday=" + birthday + ", address=" + address + "]"; } }
-
创建全局配置文件SqlMapConfig.xml(在src下创建)–配置数据源
<?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> <!-- 配置mybatis的环境信息 --> <environments default="development"> <environment id="development"> <!-- 配置JDBC事务控制,由mybatis进行管理 --> <transactionManager type="JDBC"></transactionManager> <!-- 配置数据源,采用dbcp连接池 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="12345"/> </dataSource> </environment> </environments> </configuration>
-
编写映射文件(在src中,创建sqlmap文件夹,在该目录下,创建User.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进行分类化管理,可以理解为SQL隔离 注意:使用mapper代理开发时,namespace有特殊且重要的作用 --> <mapper namespace="test"> <!-- [id]:statement的id,要求在命名空间内唯一 [parameterType]:入参的java类型 [resultType]:查询出的单条结果集对应的java类型 [#{}]: 表示一个占位符? [#{id}]:表示该占位符待接收参数的名称为id。注意:如果参数为简单类型时,#{}里面的参数名称可以是任意定义 --> <select id="findUserById" parameterType="int" resultType="com.gyf.domain.User"> SELECT * FROM USER WHERE id = #{id} </select> </mapper>
-
加载映射文件,在SqlMapConfig.xml中进行加载;(在全局配置文件中添加)
<!--告诉mybatis要加载映射文件--> <mappers> <mapper resource="com/mdy/sqlmap/User.xml"></mapper> </mappers>
-
编写测试程序,即编写Java代码,连接并操作数据库
思路:
- 读取配置文件;
- 通过SqlSessionFcatoryBuilder创建SqlSessionFactory会话工厂
- 通过SqlSessionFactory创建SqlSession
- 调用SqlSession的操作数据库的方法
- 关闭SqlSession
package com.mdy.test; import com.mdy.model.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import java.io.IOException; import java.io.InputStream; /* 1. 读取配置文件; 2. 通过SqlSessionFcatoryBuilder创建SqlSessionFactory会话工厂 3. 通过SqlSessionFactory创建SqlSession 4. 调用SqlSession的操作数据库的方法 5. 关闭SqlSession */ public class Demo1 { @Test public void test1() throws IOException { //读取配置文件; InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); //通过SqlSessionFcatoryBuilder创建SqlSessionFactory会ss话工厂 SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(is); //通过SqlSessionFactory创建SqlSession SqlSession ss = sf.openSession(); //调用SqlSession的操作数据库的方法 //查询一条语句 User user = ss.selectOne("findUserById",10); System.out.println(user); //关闭SqlSession ss.commit(); } }
-
更多案例、
-
模糊查询用户信息
- 只需要改变映射文件内容
在当前添加一行模糊查询的代码
<!-- [${}]:表示拼接SQL字符串 [${value}]:表示要拼接的是简单类型参数。 简单类型:int,...,还包括Strng 注意: 1、如果参数为简单类型时,${}里面的参数名称必须为value 2、${}会引起SQL注入,一般情况下不推荐使用。但是有些场景必须使用${},比如order by ${colname} --> <select id="findUserByName" parameterType="String" resultType="com.mdy.model.User"> SELECT * FROM USER WHERE username like '%${value}%'; </select>
- 改变测试类中调用SqlSession的操作数据库的方法(查询多条语句)
List<User> users = ss.selectList("findUserByName","张"); System.out.println(users);
-
-
插入用户信息
-
简单的插入一条语句
-
在映射文件中添加插入语句
<insert id="insertUser" parameterType="com.mdy.model.User"> insert into user(username,sex,birthday,address) values(#{username},#{sex},#{birthday},#{address}) </insert>
-
在测试类中
@Test public void test2() throws IOException{ //调用SqlSession操作数据的方法 //创建User类对象 User user = new User("枫林","男",new Date(),"江苏南京"); ss.insert("insertUser",user); }
-
-
-
更新用户信息
-
在映射文件中添加更新语句
<!--更新用户信息--> <update id="updateUser" parameterType="com.mdy.model.User"> update user set username = #{username} where id = #{id} </update>
-
在测试类中添加
/* * 更新一条数据 * */ @Test public void test4() throws IOException{ //创建User类对象 User user = new User(); //只读需要更新的主键 user.setId(27); user.setUsername("枫林"); //此方法放回的是受影响的行数,进行数据的传递时,需要通过model来传递 int row = ss.update("updateUser",user); //需要自己手动进行事务提交 ss.commit(); System.out.println(row); }
-
-
删除一条记录
-
在映射文件中添加
<!--删除一条事务信息--> <delete id="deleteID" parameterType="int"> delete from user where id = #{id}; </delete>
-
在测试类中添加调用方法
/* * 删除一条数据 * */ @Test public void test3() throws IOException{ int row = ss.delete("deleteID",29); //需要自己手动进行事务提交 ss.commit(); System.out.println(row); }
-
-
主键返回之mysql自增主键
-
思路:
-
mysql自增主键,是指在insert之前会自动生成一个自增主键
-
可以通过Mysql的函数获取到刚刚插入的自增主键
last_insert_id()
-
上面的函数是在insert语句之后去调用的
-
-
实现方式,在映射文件插入标签中加入以下代码
[selectKey标签]:通过select查询来生成主键
[keyProperty]:指定存放生成主键的属性
[resultType]:生成主键所对应的Java类型
[order]:指定该查询主键SQL语句的执行顺序,相对于insert语句(至为before时:代表在插入前返回上一条语句的主键,after:代表返回当前插入的主键)
[last_insert_id]:MySQL的函数,要配合insert语句一起使用<insert id="insertUser" parameterType="com.gyf.domain.User"> <!-- [selectKey标签]:通过select查询来生成主键 [keyProperty]:指定存放生成主键的属性 [resultType]:生成主键所对应的Java类型 [order]:指定该查询主键SQL语句的执行顺序,相对于insert语句 [last_insert_id]:MySQL的函数,要配合insert语句一起使用 --> <selectKey keyProperty="id" resultType="int" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> <!-- 如果主键的值是通过MySQL自增机制生成的,那么我们此处不需要再显示的给ID赋值 --> INSERT INTO USER (username,sex,birthday,address) VALUES(#{username},#{sex},#{birthday},#{address}) </insert>
-
测试类中添加方法
/ * * 插入一条数据后,往模型中设置id * */ @Test public void test5() throws IOException{ //创建User类对象 User user = new User("林耿","男",new Date(),"江苏扬州"); //此方法放回的是受影响的行数 int row = ss.insert("insertUser",user); //需要自己手动进行事务提交 ss.commit(); System.out.println(row); //输出主键 System.out.println("插入数据的主键:"+user.getId()); }
-
-
主键返回之mysql自增UUID
-
只需要更改插入映射文件中的返回主键部分代码
<insert id="insertUser" parameterType="com.gyf.domain.User"> <selectKey keyProperty="id" resultType="String" order="BEFORE"> SELECT UUID() </selectKey> <!-- 如果主键的值是通过MySQL自增机制生成的,那么我们此处不需要再显示的给ID赋值 --> INSERT INTO USER (username,sex,birthday,address) VALUES(#{username},#{sex},#{birthday},#{address}) </insert>
-
小结
parameterType和resultType
parameterType:指定SqlSession操作数据方法中输入参数的Java类型,可以填别名,或Java的全限定名;
resultType:指定输出结果的Java类型,可以填写别名或者Java的全限定名;
#{}和${}
#{}:相当于预处理中的占位符?。
#{}里面的参数表示接收java输入参数的名称。
#{}可以接受HashMap、POJO类型的参数。
当接受简单类型的参数时,#{}里面可以是value,也可以是其他。
#{}可以防止SQL注入。
${}:相当于拼接SQL串,对传入的值不做任何解释的原样输出。
${}会引起SQL注入,所以要谨慎使用。
${}可以接受HashMap、POJO类型的参数。
当接受简单类型的参数时,${}里面只能是value。
selectOne和selectList
selectOne:只能查询0或1条记录,大于1条记录的话,会报错:
selectList:可以查询0或N条记录
2.3 msbatis的Dao的编写(一般不用,会由更好方式)
-
DAO
- 在使用DAO时,需要编写Dao接口和接口实ss现类,将获取session和操作方法放到dao中实现
-
定义别名:在全局配置文件中添加以下标签
<!--定义别名--> <typeAliases> <typeAlias type="com.mdy.model.User" alias="user"/> </typeAliases>
2.4 mybatis 的Dao编写【mapper代理实现方式】
- Mapper代理的开发方式,程序员只需要编写mapper接口(相当于dao接口)即可。Mybatis会自动的为mapper接口生成动态代理实现类。
- 不过要实现mapper代理的开发方式,需要遵循一些开发规范。
开发规范:
- mapper接口的全限定名要和mapper映射文件的namespace的值相同;
- mapper接口的方法名称要和mapper映射文件中的statement的id相同;
- mapper接口的方法参数只能有一个,且类型要和mapper映射文件中statement的parameterType的值保持一致;
- mapper接口的返回值类型要和mapper映射文件中statement的resultType值或resultMap中的type值保持一致;
2.5 加载映射文件的方式
-
使用相对类路径的资源(写映射文件名)
<mappers> <mapper resource="com/mdy/sqlmap/User.xml"></mapper> </mappers>
-
使用mapper接口的权限定名(一般此种方式使用注解)
<mappers> <mapper class="com.mdy.mapper.UserMapper"></mapper> </mappers>
-
写包名(要求映射文件和mapper接口在一个同路径下的同目录中)
<mappers> <package name="com.mdy.mapper"/> </mappers>
第三章 mybatis映射文件
3.1输入映射ParameterType
指定输入参数的java类型,可以使用别名或类的全限定名,可以接受简单类型,POJO对象,HashMap
-
简单类型:int double …或者String
-
POJO对象:就是实体类的的全限定类名或者别名,也可以是包装类
-
hashMap:使用Map方式流程如下
-
在Mapper接口中定义一个方法,传入map参数。
public List<User> findUserListByMap(Map<String,Object> map);
-
在相应的映射文件中添加一条语句,注意传入的参数值为hashmap,
<!--使用map方式查询--> <select id="findUserListByMap" parameterType="hashmap" resultType="com.mdy.model.User"> select * from user where username = #{username}; </select>
-
使用方式
public void test2() throws Exception{ ss = ssf.openSession(); UserMapper um = ss.getMapper(UserMapper.class); //定义一个map对象 Map<String,Object> map = new HashMap<String, Object>(); //存放值 map.put("username","林"); //传入map对象 List<User> list = um.findUserListByMap(map); System.out.println(list); }
-
3.2 输出映射resultType/resultMap
-
resultType
使用resultType进行结果集映射时,查询的列名和映射的pojo属性名完全一致,该列才能映射成功
如果查询的列名和映射的pojo属性名全部不一致,则不会创建pojo对象
如果查询的列名和映射的pojo属性名有一个一致,就会创建pojo对象
-
输出类型是简单类型
例如:count(*),resultType=“int”
在Mapper接口中添加一个方法
public int findUserCount(Map<String,Object> map);
在映射文件添加一个查询,输入是hashmap,输出结果是int
<select id="findUserCount" parameterType="hashmap" resultType="int"> select count(*) from user where sex = #{sex}; </select>
在测试中调用
@Test public void test3() throws Exception{ ss = ssf.openSession(); UserMapper um = ss.getMapper(UserMapper.class); //定义一个map对象 Map<String,Object> map = new HashMap<String, Object>(); map.put("sex","男"); int count = um.findUserCount(map); System.out.println("男生的个数:"+count); }
-
输出单个pojo对象
在resultType中是pojo类的权限定名或者别名
<select id="findUserListByMap" parameterType="hashmap" resultType="com.mdy.model.User"> select * from user where username = #{username}; </select>
-
输出列表pojo对象,同上一样
注:上面两个的resultType的类型都一样,在Mapper接口中返回值类型不同,单个是该类User,列表是List
-
-
resultMap
如查询出来的列名和属性名不一致时,通过定义一个resultMap将列名和pojo属性名之间作一个映射关系
-
定义resultMap,在映射文件中定义
<!--映射关系 type:是实体类 --> <resultMap id="userResultMap" type="com.mdy.model.User"> <!-- id:一般是表中主键,其他用result property:是实体类中的属性, column:是数据库查询列--> <id property="id" column="id_"></id> <result property="username" column="username_"></result> <result property="sex" column="sex_"></result> </resultMap>
-
使用resultMap作为statement的输出映射类型,resultMap中的值为上面定义的resultMap标签的id
<select id="findUserByResultMap" parameterType="" resultMap="userResultMap"> select id id_,username username_,sex sex_ from user where id = #{id} </select>
-
第四章 动态sql
4.1 if 和 where
-
if标签:作为判断入参来使用,如果符合条件,则把if标签内的sql拼接上,
用if进行判断是否为空时,不仅要判断null,也要判断空字符串’ ';
-
where标签:会去掉条件中的第一个and符号
<select id="fndUser" parameterType="hashmap" resultType="com.mdy.model.User">
select * from user
<where>
<if test="sex != null and sex != ''">
sex = #{sex}
</if>
<if test="name != null and name !=''">
and username like '%${name}%'
</if>
</where>
</select>
4.2 sql片段
sql片段的就是将动态sql提取出来,提高SQL的可重用性
方法:在映射文件添加标签,将if语句存放进去,在查询中where标签中引用>
<!--sql片段-->
<sql id="select_user_where">
<if test="sex != null and sex != ''">
sex = #{sex}
</if>
<if test="name != null and name !=''">
and username like '%${name}%'
</if>
</sql>
<select id="fndUser" parameterType="hashmap" resultType="com.mdy.model.User">
select * from user
<where>
<include refid="select_user_where"></include>
</where>
</select>
4.3 foreach遍历
例如 select * from user where id in (31,32,22)
-
定义一个包装类,在类中定义 List ids
package com.mdy.vo; import com.mdy.model.User; import java.util.List; public class UserQueryVo { private User usr; private List<Integer> ids; public User getUsr() { return usr; } public void setUsr(User usr) { this.usr = usr; } public List<Integer> getIds() { return ids; } public void setIds(List<Integer> ids) { this.ids = ids; } }
-
在映射文件中的if和where标签中添加foreach标签
foreach标签:表示一个foreach循环
collection:集合参数的名称,如果是直接传入集合参数,则该处的参数名称只能填写list
item:每次遍历出来的对象,
open:开始遍历时拼接的串
close:结束遍历时拼接的串
separator:每次遍历出对象之间需要拼接的字符
<select id="findUserByVo" parameterType="com.mdy.vo.UserQueryVo" resultType="com.mdy.model.User"> select * from user <where> <if test="ids != null and ids.size > 0"> <foreach collection="ids" item="id" open="and id in (" close=")" separator=","> #{id} </foreach> </if> </where> </select>
-
测试类中调用
@Test public void test5() throws Exception{ ss = ssf.openSession(); UserMapper um = ss.getMapper(UserMapper.class); //定义一个包装类对象 UserQueryVo uqv = new UserQueryVo(); List<Integer> ids = new ArrayList<>(); ids.add(27); ids.add(31); ids.add(25); uqv.setIds(ids); List<User> user = um.findUserByVo(uqv); System.out.println(user); }
-
foreach遍历传入参数方式二:参数是数组,直接传入List集合
-
在接口中定义一个方法,参数为List
public List<User> findUserByList(List<Integer> list);
-
在映射文件中添加,如果参数是数组的话,输入参数为java.util.List也可是别名list,集合参数为 list(固定)
<select id="findUserByList" parameterType="java.util.List" resultType="com.mdy.model.User"> select * from user <where> <if test="list != null and list.size() > 0"> <foreach collection="list" item="id" open="and id in (" close=")" separator=","> #{id} </foreach> </if> </where> </select>
第五章 全局配置文件其他配置
5.1 properties数据库文件配置
- 在src下配置个db.properties文件
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://loaclhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
username=root
password=12344
- 修改主配置文件在configuration标签中添加
<properties resource="db.properties"/>
- 修改标签中代码
<property name="driver" value="${driverClass}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
5.2 setting【后续】
<settings>
<setting name="" value=""></setting>
</settings>
5.3 typeAliases 起别名
使用别名是为了在映射文件中,更方便的指定参数和结果集,不用写很长的全限定名
系统支持的别名列表:
_byte | byte |
---|---|
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
自定义别名
在主配置文件中添加
<typeAliases>
<!--单个别名-->
<typeAlias type="com.mdy.model.User" alias="user"></typeAlias>
<!--批量配置别名-->
<!--name,指定批量定义别名的类包,别名为类名-->
<package name="com.mdy.model"></package>
</typeAliases>
第六章 mybatis和hibernate的区别
-
mybatis技术特点
优点:
- 通过直接编写SQL语句,可以直接对SQL进行性能的优化;
- 学习门槛低,学习成本低,只要有sql基础,就可以学习Mybatis,而且很容易上手;
- 由于直接编写sql语句,所以灵活多变,代码维护性更好;
缺点:
- 不能支持数据库无关性,即数据库发生变更,要写多套代码进行支持,移植性不好
- 分页Mysql:limit
- 分页Oracle:rownum
- 需要编写结果映射
-
hibernate技术特点
优点:
- 标准的ORM框架,程序员不需要写sql语句;
- 具有良好的数据库无关性,即数据库发生变化,代码无需再次编写
- 在mysql迁移到Oracle时,只需改变配置
缺点:
- 学习门槛高,需要对数据模型有良好的基础,而且在设置ORM映射时,需要考虑好性能和对象模型的
- 程序员不能自主的去进行SQL性能优化
-
Mybatis的应用场景
- 需求多变的互联网项目,如电商项目
-
Hibernate的应用场景
- 需求明确,业务固定的项目,例如OA项目,ERP项目