MyBatis
MyBatis
一、简介
- 是一个 持久层(DAO) 框架
- 是一个ORM(Object Relation Mapping--> RowMapper)框架
- 对 持久层技术(JDBC)的封装
- JDBC 是 Java 访问数据库的 唯一途径
- 代码托管在github上
二、关于 持久层框架
-
ORM 框架 对象关系映射
-
所有的ORM框架都是对JDBC的封装
-
所有的ORM框架关心的:
-
数据源(Datasource)
数据库连接信息
url,driverClassName,username,password -
SQL语句
Mybatis:程序员自己写
Hibernate: HQL ---》 SQL
-
RM , 关系映射(表<-->实体类 一条数据<-->一个java对象 列 <--->类属性)
-
三、HelloWorld
0、建表、建库
create database mybatis;
use mybatis;
create table t_user(
id int primary key auto_increment,
name varchar(200),
pwd varchar(200),
age int
);
1、添加dtd文件(选做)
-
mybatis-confit.dtd
public id : -//mybatis.org//DTD Config 3.0//EN
-
mybatis-mapper.dtd
public id :"-//mybatis.org//DTD Mapper 3.0//EN"
2、添加jar包
- mybatis.jar
- 数据库的jar包
3、配置config文件
在src(类加载路径)下,创建mybatis-config.dtd
填入以下内容
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "mybatis-3-config.dtd" >
<configuration>
<environments default="jdbc">
<environment id="jdbc">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
</configuration>
4、配置mapper文件
在 day01.dao.mapper下创建 userMapper.xml, 编写以下代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd" >
<mapper namespace="abc">
<insert id="ins">
insert into
t_user
(name,pwd,age)
values
('zhangsan','123',22)
</insert>
</mapper>
在config中配置mapper
<environments default="jdbc">
</environments>
<mappers>
<!-- 写的是路径-->
<mapper resource="day01/dao/mapper/userMapper.xml"/>
</mappers>
5、编写java代码
package day01.test;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class HelloWorld {
public static void main(String[] args) {
// 创建Sq1Session的工厂
SqlSessionFactory sf = new SqlSessionFactoryBuilder()
.build(
// 参数是一个流,指定config文件的位置
HelloWorld.class
.getClassLoader()
.getResourceAsStream("mybatis-config.xml")
);
//Mybatis的核心类
// 打开一个会话/开启一个事务
SqlSession session = sf.openSession();
// 执行对应的sql语句
session.insert("abc.ins");
// 提交事务
session.commit();
System.out.println("over");
}
}
6、打开调试(debug)
加入log4j的jar包 以及 配置文件 将日志等级设置为debug
四、HelloWorld-2
前四步不变
1、编写dao接口
package day01.dao;
public interface UserDao {
public void insertUser();
}
2、配置mapper文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd" >
<!-- namespace 写dao接口的包名.类名 -->
<mapper namespace="day01.dao.UserDao">
<!-- id值 写 dao接口中的方法名 -->
<insert id="insertUser">
insert into
t_user
(name,pwd,age)
values
('zhangsan','123',22)
</insert>
</mapper>
3、编写测试类
package day01.test;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import day01.dao.UserDao;
public class HelloWorld2 {
public static void main(String[] args) {
// 创建Sq1Session的工厂
SqlSessionFactory sf = new SqlSessionFactoryBuilder()
.build(
// 参数是一个流,指定config文件的位置
HelloWorld2.class
.getClassLoader()
.getResourceAsStream("mybatis-config.xml")
);
//Mybatis的核心类
// 打开一个会话/开启一个事务
SqlSession session = sf.openSession();
// 获取dao实现类对象
UserDao userDao = session.getMapper(UserDao.class);
userDao.insertUser();
// 提交事务
session.commit();
System.out.println("over");
}
}
五、config文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "mybatis-3-config.dtd" >
<configuration>
<!-- 配置类的别名 -->
<typeAliases>
<!-- 为 day01.entity.User 取别名 叫做 User
所有使用day01.entity.User的地方都可以使用User代替-->
<!--<typeAlias type="day01.entity.User" alias="User"></typeAlias> -->
<!-- 为day01.entity包中的所有类取别名。别名就是简单类名 -->
<package name="day01.entity"></package>
</typeAliases>
<!--配置环境
myBatis 支持多数据源配置
default 指定了默认的数据源,值是某个数据源的id值
-->
<environments default="jdbc">
<!--配置一个数据源 id 是该数据源的唯一标志-->
<environment id="jdbc">
<!--事务管理器
type:jdbc
使用jdbc的方式管理事务
mybatis依赖于Connection对象管理事务
managed
如果设置为managed,mybatis将不再关心事务
事务的管理将交给其他容器(框架)(tomcat、spring)
-->
<transactionManager type="JDBC"></transactionManager>
<!--配置数据源/数据库连接信息
type:
pooled:池,使用数据库连接池管理connection
unpooled: 不使用连接池,每次访问数据库,获取新的
Connection
jndi :向其他容器(框架)要连接对象
-->
<dataSource type="POOLED">
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
<!--指定mapper文件的位置-->
<mappers>
<mapper resource="day01/dao/mapper/userMapper.xml"/>
</mappers>
</configuration>
六、mapper文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd" >
<!-- namespace
1. session.insert();
namespace可以随便写,insert方法的参数是namespace . id
2. dao.insert()
namespace必须是dao的 包名.类名
-->
<mapper namespace="day01.dao.UserDao">
<!--
1. session.insert();
id可以随便写,insert方法的参数是namespace . id
2. dao.insert()
id值 写 dao接口中的方法名
--》 id 值要唯一
--》 dao中不存在同名方法
--》 dao中的方法不能重载
-->
<insert id="insertUser">
insert into
t_user
(name,pwd,age)
values
('zhangsan','123',22)
</insert>
<!-- 根据id删除 注意:不写参数 -->
</mapper>
七、单表操作
1、基本增删改查操作
<!-- id值 写 dao接口中的方法名 -->
<insert id="insertUser">
insert into
t_user
(name,pwd,age)
values
('zhangsan','123',22)
</insert>
<!-- 根据id删除 注意:不写参数 -->
<delete id="delete">
delete
from
t_user
where
id = 5
</delete>
<update id="update">
update
t_user
set
name='abc'
where
id = 7
</update>
<!-- resultType结果的类型 -->
<select id="selectAll" resultType="day01.entity.User">
select
id,
name,
pwd,
age
from
t_user
</select>
public void insertUser();
public void delete();
public void update();
public List<User> selectAll();
SqlSession session = sf.openSession();
// 获取dao实现类对象
UserDao userDao = session.getMapper(UserDao.class);
userDao.insertUser();
userDao.update();
userDao.delete();
userList = userDao.selectAll();
System.out.println(userList);
// 提交事务
session.commit();
2、保存返回主键
<!--
useGeneratedKeys 启用保存返回主键功能
keyColumn 主键对应的列名
keyProperty 将主键值放入方法参数的哪个属性中
-->
<insert id="insert"
useGeneratedKeys="true"
keyColumn="id"
keyProperty="id"
>
insert into
t_user
(name,pwd,age)
values
(#{name},#{pwd},#{age})
</insert>
User user = new User(null, "abc", "123", 99);
userDao.insert(user);
System.out.println(user.getId());
3、对象关系映射
3-1、默认情况
myBatis 会将查询结果封装成对象,对应关系是 将对应列的值放到同名属性中
3-2、使用重命名
CREATE TABLE `t_user2` (
`id` int(11) NOT NULL DEFAULT '0',
`username` varchar(200) DEFAULT NULL,
`password` varchar(200) DEFAULT NULL,
`age` int(11) DEFAULT NULL
)
package day01.entity;
public class User {
private Integer id;
private String name;
private String pwd;
private Integer age;
//get set
}
<select id="selectAll2" resultType="day01.entity.User">
select
id,
username as name,
password as pwd,
age
from
t_user2
</select>
3-3、使用resultMap
<!--
1.位置:在insert/update。。。前面
2.作用:定义映射关系
3.id:resultMap的唯一标识
4.type:最终封装的对象类型
-->
<resultMap type="day01.entity.User" id="rm1">
<id column="id" property="id"/>
<result column="password" property="pwd"/>
<result column="age" property="age"/>
<result column="username" property="name"/>
</resultMap>
<select id="selectAll3" resultMap="rm1">
select
id,
username,
password,
age
from
t_user2
</select>
4、方法参数
Mybatis dao方法支持写0个或者多个参数,但是官方建议最多写一个参数
4-1 对象类型参数
使用#{xx} 的方式 获取参数的属性值
#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。
${} $将传入的数据直接显示生成在sql中。
-
方式能够很大程度防止sql注入,$方式无法防止Sql注入。
-
$方式一般用于传入数据库对象,例如传入表名。
-
从安全性上考虑,能使用#尽量使用#来传参,因为这样可以有效防止SQL注入的问题。
#将值加双引号,$直接传入不做处理
// dao 接口
public List<User> selectByUser(User user);
<!--mapper文件-->
<!--
使用#{name} 获取user参数的name属性的值
-->
<select id="selectByUser" resultType="User">
select
id,
name,
pwd,
age
from
t_user
where
name = #{name}
</select>
4-2 map类型参数
用法 和 对象类型一致,只是 #{}中写的是 map的 key值
//dao 接口
public List<User> selectByMap(Map<String, Object> map);
SqlSessionFactory sf = new SqlSessionFactoryBuilder()
.build(Test01.class.getClassLoader().getResourceAsStream("mybatis-config.xml"));
SqlSession session = sf.openSession();
UserDao userDao = session.getMapper(UserDao.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "zhangsan");
List<User> userList = userDao.selectByMap(map);
System.out.println(userList);
<!--mapper-->
<select id="selectByMap" resultType="User">
select
id,
name,
pwd,
age
from
t_user
where
name = #{name}
</select>
4-3 简单类型参数
基本数据类型、包装类、字符串等等
//dao 接口
public List<User> selectByName(String name);
<select id="selectByName" resultType="User">
select
id,
name,
pwd,
age
from
t_user
where
name = #{name}
</select>
如果dao方法参数只有一个,并且是简单类型
#{}中的内容可以随便写- 建议使用 参数名
- 万能写法(建议写法): 配合dao接口中的一个注解使用
//dao
// @Param("username") 为name参数取别名,叫做username
public List<User> selectByName2( @Param("username") String name);
<!--由于dao方法参数上加了 @Param("username") 注解,所以只能写#{username}-->
<select id="selectByName2" resultType="User">
select
id,
name,
pwd,
age
from
t_user
where
name = #{username}
</select>
4-4 多个参数
MyBatis 本身不建议写多个参数,官方文档中也不存在多个参数的例子
- 默认解决方案
// dao接口
public List<User> selectByNameAndPwd(String name,String pwd);
<select id="selectByNameAndPwd" resultType="User">
select
id,
name,
pwd,
age
from
t_user
where
name = #{param1}/#{0}
and
pwd = #{param2}/#{1}
</select>
- 万能解决方案
public List<User> selectByNameAndPwd2(
@Param("username") String name,
@Param("password") String pwd);
<select id="selectByNameAndPwd2" resultType="User">
select
id,
name,
pwd,
age
from
t_user
where
name = #{username}
and
pwd = #{password}
</select>
八、动态SQL
mybatis中提供了一系列的标签,用于生成动态sql
1、if
//dao接口
/**
* 条件查询,如果name不为null name作为查询条件,如果pwd不为null pwd作为查询条件,
* @param user
* @return
*/
public List<User> selectFuzzy(User user);
<!-- 在标签中获取参数的属性,不需要#{}
if 标签用法类似于jstl的c:if
-->
<select id="selectFuzzy" resultType="User">
select
id,
name,
pwd,
age
from
t_user
where
1=1
<if test="name != null">
and name = #{name}
</if>
<if test="pwd != null">
and pwd = #{pwd}
</if>
</select>
案例
<update id="update">
update
t_user
set
<if test="name != null">
name = #{name},
</if>
<if test="pwd != null">
pwd = #{pwd},
</if>
<if test="age != null">
age = #{age},
</if>
id = #{id}
where
id = #{id}
</update>
<select id="selectByAge" resultType="User">
select
<include refid="selCols"></include>
from
t_user
where
<if test="age != null">
age = #{age}
</if>
<if test="age == null">
age is null
</if>
</select>
2、where
<!-- 替代where关键字
删除多余的and|or
-->
<select id="selectFuzzy2" resultType="User">
select
id,
name,
pwd,
age
from
t_user
<where>
<if test="name != null">
and name = #{name}
</if>
<if test="pwd != null">
and pwd = #{pwd}
</if>
</where>
</select>
3、set
<!-- 替代set关键字
删除多余的逗号
-->
<update id="update">
update
t_user
<set>
<if test="name != null">
name = #{name},
</if>
<if test="pwd != null">
pwd = #{pwd},
</if>
<if test="age != null">
age = #{age},
</if>
</set>
where
id = #{id}
</update>
4、trim
- 在标签头 或者 尾添加内容
- 删除标签头 或者 标签尾的内容
<select id="selectFuzzy3" resultType="User">
select
id,
name,
pwd,
age
from
t_user
<trim prefix="where" prefixOverrides="and">
<if test="name != null">
and name = #{name}
</if>
<if test="pwd != null">
and pwd = #{pwd}
</if>
</trim>
</select>
<update id="update3">
update
t_user
<trim prefix="set" prefixOverrides="," >
<if test="name != null">
,name = #{name}
</if>
<if test="pwd != null">
,pwd = #{pwd}
</if>
<if test="age != null">
,age = #{age}
</if>
</trim>
where
id = #{id}
</update>
5、foreach
用于遍历
<!--
foreach
collection
如果参数类型是List -》 list
如果参数类型是数组 -》 array
如果参数类型是 对象,并且要遍历的是对象属性 -》 属性名
如果参数类型是 map,并且要遍历的是map中的某个值-》 map的key
item 每次遍历的元素的别名
separator 每次遍历的元素之间的分隔符
open 遍历开始前的字符
close 遍历结束后的字符
-->
<delete id="deleteBatch">
delete
from
t_user
where
id in
<!--(2,34,56,7,89,0)
2,34,56,7,89,0
c:foreach items="" var="d"
-->
<foreach collection="array" item="id"
separator="," open="(" close=")">
#{id}
</foreach>
</delete>
<delete id="deleteBatch2">
delete
from
t_user
where
id in
<foreach collection="deleteIds" item="id"
separator="," open="(" close=")">
#{id}
</foreach>
</delete>
//dao 接口
public void deleteBatch(int[] ids);
public void deleteBatch2(Map<String, Integer[]> map);
6、bind
/**
* 根据名字 模糊查询
* @param name
* @return
*/
public List<User> selectByNameFuzzy(@Param("name") String name);
<select id="selectByNameFuzzy" resultType="User">
<!-- 绑定一个变量n,n的值是 '%' + name + '%'-->
<bind name="n" value=" '%' + name + '%' "/>
select
<include refid="selCols"></include>
from
t_user
where
name like #{n}
</select>
7、choose-when-otherwise
//dao 接口
public List<User> selectByAge2(@Param("age") String age);
<!--
注意转义 &
-->
<select id="selectByAge2" resultType="User">
select
<include refid="selCols"></include>
from
t_user
where
<choose>
<when test="age == -1">
1=1
</when>
<when test="age != null && age != '' ">
age = #{age}
</when>
<otherwise>
age is null
</otherwise>
</choose>
</select>
九、多表关系映射
1、准备工作
-
建表
set names utf8; use mybatis; create table t_hero_type( id int primary key auto_increment, name varchar(200) )engine=Innodb default charset=utf8; insert into t_hero_type (name) values ('AD'); insert into t_hero_type (name) values ('AP'); insert into t_hero_type (name) values ('ADC'); create table t_hero( id int primary key auto_increment, name varchar(200), typeId int, foreign key(typeId) references t_hero_type(id) )engine=Innodb default charset=utf8; insert into t_hero (name,typeId) values('jax',1); insert into t_hero (name,typeId) values('gay',1); insert into t_hero (name,typeId) values('cindera',2); insert into t_hero (name,typeId) values('razi',2); insert into t_hero (name,typeId) values('Catalina',3); -
实体类
public class HeroType { private Integer id; private String name; private Set<Hero> heros = new HashSet<Hero>(); // constructer // get / set } public class Hero { private Integer id; private String name; private HeroType heroType; // constructer // get / set } -
多表查询
-
笛卡尔积
将一张表的每条数据和另一张表的每条数据进行无条件连接
select * from t_hero,t_hero_type -
连接查询
将一张表的每条数据 和 另一张表的每条数据 根据 连接条件 进行连接
当且仅当 两条数据 满足 连接条件时 该数据才会作为 最终的 查询结果
约定:from的表叫做 左表,连接的表叫做 右表
-
内连接 ( inner join )
所有的左表中能够在右表找到满足连接条件的数据
-
左外连接 ( left join )
所有的左表中的数据,如果在右表中找不到对应的数据,右表的字段补NULL
-
右外连接 (right join)
所有的右表中的数据,如果在左表中找不到对应的数据,左表的字段补NULL
连接查询语法
连接方式 表名 on 连接条件 -

-
2、多对一查询
从t_hero 查询 t_hero_type
最终查询的是Hero对象
2-1 方式1
<!--
ht.id as 'heroType.id',
将查询结果中的ht.id列的名字 重命名为 heroType.id
即:将查询结果中的ht.id这个列对应到 Hero 的 heroType属性 的 id属性上
-->
<select id="selectAll" resultType="Hero">
select
h.id,
h.name,
h.typeId,
ht.id as 'heroType.id',
ht.name as 'heroType.name'
from
t_hero h
inner join t_hero_type ht on h.typeId = ht.id
</select>
2-2 方式2 (最常用)
<resultMap type="Hero" id="rm2">
<id column="id" property="id"/>
<result column="name" property="name"/>
<!-- 配置多对一关系
property: 一方(Hero中的HeroType)的属性名
javaType: property 对应的属性的java类型
-->
<association property="heroType" javaType="HeroType">
<!-- 此处内容语法和外部的resultMap一致 ,用于映射一方对象-->
<id column="htid" property="id"/>
<result column="htname" property="name"/>
</association>
</resultMap>
<select id="selectAll2" resultMap="rm2">
select
h.id,
h.name,
h.typeId,
ht.id htid,
ht.name htname
from
t_hero h
inner join t_hero_type ht on h.typeId = ht.id
</select>
2-3 方式3
<resultMap type="Hero" id="rm3">
<id column="id" property="id"/>
<result column="name" property="name"/>
<!--
resultMap
引用其他resultMap配置
如果其他的resultMap 配置在当前mapper文件中,
可以直接写resultMap的id值
如果其他的resultMap 配置在其他mapper文件中,
使用其他mapper文件的namespace.id
-->
<association property="heroType" javaType="HeroType" resultMap="day03.dao.HeroTypeDao.htrm">
</association>
</resultMap>
<select id="selectAll3" resultMap="rm3">
select
h.id,
h.name,
h.typeId,
ht.id htid,
ht.name htname
from
t_hero h
inner join t_hero_type ht on h.typeId = ht.id
</select>
heroTypeMapper.xml
<resultMap type="HeroType" id="htrm">
<id column="htid" property="id"/>
<result column="htname" property="name"/>
</resultMap>
2-4 方式4(不建议使用)
<!--
select 另外一个查询的id
column 查询条件,对应当前查询的某个列
效率低。进行了多次查询,N + 1 次
1次: 所有的Hero对象
N次:每个Hero对象对应N次中的一次
-->
<resultMap type="Hero" id="rm4">
<id column="id" property="id"/>
<result property="name" column="name"/>
<association property="heroType"
javaType="HeroType"
select="day03.dao.HeroTypeDao.selectById"
column="typeId"
></association>
</resultMap>
<select id="selectAll4" resultMap="rm4">
select
id,
name,
typeId
from
t_hero
</select>
<resultMap type="HeroType" id="htrm">
<id column="htid" property="id"/>
<result column="htname" property="name"/>
</resultMap>
<select id="selectById" resultType="HeroType">
select
id,
name
from
t_hero_type
where
id = #{id}
</select>
3、一对多查询
从t_hero_type 查询 t_hero
最终查询的是HeroType对象
同样用三种方式(多对一的方式2到方式4),只是要将
association改成CollectionjavaType改成ofType
十、分页
可以使用MyBatis分页插件事件分页功能
1、导入jar包
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>最新版本</version>
</dependency>
2、配置插件
在config文件中。使用plugin配置分页插件
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 4.0.0以后版本可以不设置该参数 -->
<property name="dialect" value="mysql"/>
<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
<property name="pageSizeZero" value="true"/>
<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
<!-- 启用合理化时,如果pageNum小于1会查询第一页,
如果pageNum大于pages会查询最后一页 -->
<!-- 禁用合理化时,如果pageNum小于1或pageNum大于pages会返回空数据 -->
<property name="reasonable" value="true"/>
</plugin>
</plugins>
3、service代码
PageHelper.startPage(2,3);
List<Hero> heroList = heroDao.selectAll5();
PageInfo<Hero> info = new PageInfo<Hero>(heroList);
System.out.println("当前页"+info.getPageNum());
System.out.println("上一页"+info.getPrePage());
System.out.println("下一页"+info.getNextPage());
System.out.println("总页数"+info.getPages());
System.out.println("数据总条数"+info.getTotal());
for (int i = 0; i < info.getList().size(); i++) {
Hero h = info.getList().get(i);
System.out.println(h);
}

浙公网安备 33010602011771号