一、Mybatis概述
1.1 什么是Mybatis?
- mybatis框架内部封装了jdbc,开发者只需要关注sql语句本身(更加关注业务需求而不是去关注其他的建立连接、释放资源等繁琐操作)mybatis通过xml或注解的方式将需要执行的各种statement配置起来,通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果集映射为java对象返回
- Mybatis中sql与Java编码分离,sql以配置文件的形式编写,由开发人员控制。其它预编译、设置参数、执行sql、封装结果、释放资源等由框架执行,属于一个轻量级的半自动持久层框架
1.2 Mybatis的依赖
- Maven导入坐标
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
</dependencies>
二、基于XML配置Mybatis
2.1 基本步骤
- 创建maven工程并导入坐标
- 创建实体类(用来映射数据库中的对象)和dao(数据访问层)的接口
- 创建mybatis的主配置文件:SqlMapConfig.xml
- 创建映射配置文件:UserDao.xml
2.2 环境搭建注意事项
mybatis的映射配置文件位置必须和dao接口的包结构相同(相同目录结构)
因为反射在读取配置文件时,对于maven工程,反射的路径是target\classes...,包结构相同,才能读取到xml文件
映射配置文件的Mapper标签namespace属性的取值必须是dao接口的全限定类名,以名称空间区分同名的类
映射配置文件的操作配置(select\update等标签),id属性的取值必须是dao接口的方法名,当遵从了以上配置后,在开发中就无须再写dao的实现类,由mybatis通过反射创建代理类。
2.3 配置示例
1.主配置文件
- SqlMapConfig.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">
<-- mybatis的主配置文件,主要用来和数据库建立连接,另外还要建立和sql映射文件的连接 -->
<configuration>
<-- 配置默认环境 -->
<environments default="mysql">
<-- 配置mysql的环境 -->
<environment id="mysql">
<-- 配置事务的类型 -- >
<transactionManager type="JDBC"></transactionManager>
<-- 配置数据源(连接池)-->
<dataSource type="POOLED">
<-- 配置连接数据库的4个基本信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
< -- 指定映射配置文件,映射配置文件指的是每个dao独立的配置文件,可以用packge直接指定某个包-->
<mapper resource="com/itheima/dao/UserDao.xml"/> //在main方法中只读取了主配置文件,因此需要把映射配置文件也放进来,映射配置文件的url,写路径的时候
</mappers>
</configuration>
- 数据库连接可用propeties文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///mybatis
username=root
password=1234
<properties resource="MapConfig.properties"/>
<dataSource type="POOLED">
<!--配置连接数据库的4个基本信息-->
<property name="driver" value="${driver}"/> //固定写法,${properties文件中的键名}
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
2.Dao接口配置文件
- Sql映射文件UserDao配置
<?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="com.itheima.dao.UserDao"> //全限定类名
<-- 配置查询所有-->
<select id="findAll" resultType="com.itheima.domain.User"> //映射到实体类
select * from employee
</select>
</mapper>
3.Dao接口配置
- 配置Dao接口
public interface UserDao {
/*
* 查询所有操作
* */
List<User> findAll();
}
4.测试类
- 测试
public class MybatisTest {
public static void main(String[] args) throws IOException {
// 1、读取配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2、创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
// 3、使用工厂生产SqlSession对象,true开启自动提交事务的功能
SqlSession session = factory.openSession(true);
// 4、使用SqlSession创建Dao接口的动态代理对象
UserDao dao = session.getMapper(UserDao.class);
// 5、使用代理对象执行方法
List<User> users = dao.findAll();
// 输出对象
for (User user : users)
System.out.println(user);
// 6、释放资源
session.close();
inputStream.close();
}
}
三、基于注解配置Mybatis
- Mybatis的接口配置只能选择注解或者xml之一,不能同时存在。
1.配置全局配置文件
修改映射路径为class。也可以使用过package属性。
<mappers>
<!--指定映射配置文件,映射配置文件指的是每个dao独立的配置文件-->
<!--如果是用注解来配置的话,此处应该左边使用class属性,右边指定被注解的dao全限定名-->
<mapper class="com.itheima.dao.UserDao"/> //因为不是用xml配置,dao接口已经被初始完毕,所以这里需要的是全限定名而不是路径
</mappers>
2.注解配置Dao接口
public interface UserDao {
/*
* 查询所有操作
* */
@Select("select * from employee") //基于方法的注解,为findAll方法传入了一个属性"select * from employee"
List<User> findAll();
}
四、综合案例
4.1 配置Dao接口
1.xml
<mapper namespace="com.itheima.mybatis.dao.UserDao">
<-- sql映射配置文件-->
< -- 配置查询所有-->
<select id="findAll" resultType="com.itheima.mybatis.domain.User">
select * from employee
</select>
<update id="update" parameterType="com.itheima.mybatis.domain.User">
update employee set lastname = #{lastname},gender = #{gender} where id = #{id}
</update>
<delete id="delete" parameterType="Integer">
delete from employee where id = #{id}
</delete>
<insert id="insert" parameterType="com.itheima.mybatis.domain.User">
insert into employee(lastname,gender,salary,age) values(#{lastname},#{gender},#{salary},#{age})
</insert>
</mapper>
2.注解
public interface UserDao {
@Select("select * from employee ")
List<User> findAll();
//注意注解中的属性一定要和User中的属性名称相同,并且只有当User中的getter&setter方法是自动生成的,才可以映射成功,#{}表示占位符,里面内容要名称一致
@Update("update employee set lastname = #{lastname},gender = #{gender} where id = #{id}") //#{}大括号里面的内容要和JavaBean属性名一致,因为这里的值是调用
public void update(User user); getter方法获得的
@Insert("insert into employee(lastname,gender,salary,age) values(#{lastname},#{gender},#{salary},#{age})")
public void insert(User user);
@Delete("delete from employee where id = #{id}") //当只有单个占位符时,里面的名称可以随意
public void delete(Integer id);
@Select("select * from employee where lastname like #{name}") //模糊查询
public List<User> moHuFind(String s);
}
4.2 测试
public class MybatisTest implements Serializable {
InputStream inputStream;
SqlSession sqlSession;
UserDao userDao;
@Before //Before注解的作用是说明在@Test之前执行
public void init() throws IOException {
inputStream = Resources.getResourceAsStream(null, "sqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = factory.openSession(true);
userDao = sqlSession.getMapper(UserDao.class);
}
@After //After注解的作用是说明在@Test之后执行
public void close() throws IOException {
sqlSession.close();
inputStream.close();
}
@Test
public void insert() {
User user = new User();
user.setAge(20);
user.setGender("女");
user.setLastname("Nana");
user.setSalary(28300.0);
userDao.insert(user);
}
@Test
public void update() {
User user = new User();
user.setId(8);
user.setAge(22);
user.setGender("女");
user.setLastname("NaNaNa");
user.setSalary(28400.0);
userDao.updata(user);
}
@Test
public void delete() {
userDao.delete(5);
}
@Test
public void query() {
List<User> list = userDao.findAll();
System.out.println(list);
}
@Test
public void moHuFind() {
List<User> list = userDao.moHuFind("%i%");
System.out.println(list);
}
}
4.3 数据库和实体类属性名不一致
- 对于实体类,如果类名称过长,可以在主配置文件中起别名
- 对于属性名不一致问题,在主配置文件中建立ResultMap进行映射
<typeAliases>
<typeAlias alias="user" type="com.Mybatis.User"/> //alias表示别名,如果不写,默认是对应类的小写名
</typeAliases>
<mapper namespace="com.Mybatis.UserDao">
<resultMap id="users" type="com.Mybatis.User"> //id可以随意取,表示该resultMap的名称
< -- id表示主键-->
<id property="userId" column="id"/> //property:实体类中属性 column:数据库中属性
<-- result表示非主键-->
<result property="userName" column="lastname"/>
<result property="userGender" column="gender"/>
<result property="userSalary" column="salary"/>
<result property="userAge" column="age"/>
</resultMap>
<select id="findAll" resultMap="users"> //这里返回类型要用resultMap而不是resultType
select * from employee
</select>
<select id="findById" parameterType="Integer" resultMap="users">
select * from employee where id = #{id}
</select>
<select id="findByName" parameterType="String" resultMap="users">
select * from employee where lastname like #{name}
</select>
<select id="findByVo" parameterType="com.Mybatis.QueryVo" resultMap="users"> //当为Vo类时,要用Vo类的对象的属性
select * from employee where lastname = #{user.userName}
</select>
</mapper>
五、动态SQL语句
- Mybatis的xml配置提供了一组动态SQL语句,可以提高SQL执行效率
1.if标签
<!--根据条件查询 <if>标签-->
<select id="findUserByCondition" parameterType="user" resultMap="users">
select * from employee where 1=1
<if test="userName != null"> //这里要使用实体类中的属性,因为是要判断用户输入的值是否是数据库中的某个值,因此输入的应该是实体类中的属性,所以实际开发中尽量让名称相同
and lastname = #{userName} //注意要有and连接,整句就相当于select * from employee where lastname = #{userName} (and userName != null)
</if>
</select>
2. Where标签
<select id="findUserByCondition" parameterType="user" resultMap="users">
select * from employee
<where> //where嵌套多个if条件
<if test="userName != null">
and lastname = #{userName}
</if>
<if test="userId != null">
and id = #{userId}
</if>
</where>
</select>
3. foreach标签
<select id="findUserByList" parameterType="queryvo" resultMap="users">
select * from employee
<where>
<if test="list != null">
<foreach collection="list" open="and id in(" close=")" item="id" separator=","> //这里的open和close表示开始和结束的符号,sql中语句为 where id in(...)
#{id}
</foreach>
</if>
</where>
</select>
List<User> findUserByList(QueryVo queryVo); //方法声明,复合查询
public class QueryVo { //QueryVo中添加list
User user;
List<Integer> list;
}
六、Mybatis中的多表查询-xml方式
6.1 一对一
- 示例中,一个account对应一个user,在account中引用user
<resultMap id="userAccountMap" type="account">
<!--account中别名-->
<id property="ID" column="aid"/> //要注意,主表和从表的id最好起不同名,不然相同的话会出现数据覆盖的BUG,如果同名的话可以在sql语句中用别名区别
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<-- 配置一对一关系映射,封装user的内容,association谁,哪个键去关联他,它是什么类型-->
<association property="user" column="uid" javaType="user">
<-- 配置封装对象user的resultMap-->
<id property="id" column="id"/>
<result property="lastname" column="lastname"/>
<result property="gender" column="gender"/>
<result property="salary" column="salary"/>
<result property="age" column="age"/>
</association>
</resultMap>
<select id="findAll" resultMap="userAccountMap">
SELECT * FROM account,employee WHERE account.`uid`=employee.`id`
</select>
6.2 一对多
- 一个user对应多个account,user has account collection
<!--配置一个用户对应多个账户的一对多连接--> //Mybatis能够做到对于相同名称的用户,将所属它的Account对象都封装到accounts集合中(通过id和uid的连接关系来判断)
<resultMap id="UserAccountMap" type="user">
<id property="id" column="id"/>
<result property="lastname" column="lastname"/>
<result property="gender" column="gender"/>
<result property="salary" column="salary"/>
<result property="age" column="age"/>
<!--配置user对象中账户集合accounts的映射,ofType:集合元素所属类型-->
<collection property="accounts" ofType="account">
<id property="ID" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
</collection>
</resultMap>
<select id="findAll" resultMap="UserAccountMap">
SELECT * FROM employee LEFT JOIN account ON employee.`id`=account.`uid` //左外连接,employee做主表
</select>
6.3 多对多
用户和角色:一个用户可以有多重角色(身份),一个角色(身份)可以赋予多个用户,多对多其实就是不同实体类的一对多
步骤
- 建立两张表:用户表和角色表让用户表和角色表具有多对多的关系,需要使用中间表,中间表中包含各自的主键,这些主键在中间表中是外键
- 建立两个实体类:用户实体类和角色实体类让用户和角色的实体类能体现出多对多的关系,各自包含对方一个集合引用
- 建立两个配置文件:用户和角色的配置文件
- 实现配置:当我们查询用户时,可以同时得到用户所包含的角色信息当我们查询角色时,可以同时得到角色所赋予的用户信息
首先创建角色表的一对多
<mapper namespace="com.Mybatis.RoleDao">
<!--定义Role表中的别名resultMap-->
<resultMap id="roleMap" type="role">
<id property="roleId" column="roleID"/>
<result property="roleName" column="ROLE_NAME"/>
<result property="roleDesc" column="ROLE_DESC"/>
<collection property="userList" ofType="user">
<id property="id" column="id"/>
<result property="lastname" column="lastname"/>
<result property="gender" column="gender"/>
<result property="salary" column="salary"/>
<result property="age" column="age"/>
</collection>
</resultMap>
<!--查询语句-->
<select id="findAll" resultMap="roleMap">
SELECT employee.*,role.ID as roleID,ROLE_NAME,ROLE_DESC FROM
role LEFT JOIN user_role ON role.`ID`=user_role.`RID`
LEFT JOIN employee ON employee.`id`=user_role.`UID`
</select>
</mapper>
然后创建用户表的一对多
<resultMap id="UserRoleMap" type="user">
<id property="id" column="id"/>
<result property="lastname" column="lastname"/>
<result property="gender" column="gender"/>
<result property="salary" column="salary"/>
<result property="age" column="age"/>
<collection property="roleList" ofType="role">
<id property="roleId" column="roleID"/>
<result property="roleName" column="ROLE_NAME"/>
<result property="roleDesc" column="ROLE_DESC"/>
</collection>
</resultMap>
<select id="findAll" resultMap="UserRoleMap">
SELECT employee.*,role.`ID` AS roleID,role.`ROLE_DESC`,role.`ROLE_NAME` FROM employee LEFT JOIN user_role ON employee.`id` = user_role.`UID`
LEFT JOIN role ON role.`ID` = user_role.`RID`
</select>
七、Mybatis中的多表查询-注解方式
1.一对一
- 一对一配置
- xml中编写了具体的多表连接语句,而注解中没有编写多表连接语句,因此要手动指定是哪个方法。
@Select("select * from account") //配置account的映射关系
@Results(id = "AccountUserMap", value = {
@Result(id = true,property = "aid",column = "aid"),
@Result(property = "uid",column = "uid"),
@Result(property = "money",column = "money"),
//配置account中一对一的user信息,column是表示关联的字段,通过该字段来获取到对应的user,one表示一对一,里面有个@one注解,包含select参数和fetchType参数
//select参数是一个sql语句的执行方法的全限定名,因为最终要根据uid查询到对应的user,因此需要的方法是user中的findByID方法,fetchType是加载策略,有LAZY,EAGER,DEFAULT三个值
@Result(property = "user",column = "uid",one = @One(select = "com.itheima.dao.UserDao.findByID",fetchType = FetchType.EAGER))
})
List<Account> findAll();
2.一对多
- 一对多配置
- 同样也要手动指定连接的方法
@Select("select * from employee")
@Results(id = "UserAccountMap", value = {
// 第一行配置主键,id默认为false,配置为true表示该键为主键
@Result(id = true,property = "id",column = "id"),
// 配置其他属性对应
@Result(property = "lastname",column = "lastname"),
@Result(property = "salary",column = "salary"),
@Result(property = "gender",column = "gender"),
@Result(property = "age",column = "age"),
@Result(property = "accounts",column = "id",
many = @Many(select = "com.itheima.dao.AccountDao.findAccountByID",fetchType = FetchType.EAGER))
})
List<User> findAll();