1. MyBatis 入门
-
ssm 框架与三层架构
-
MyBatis 是一个持久层框架,用 Java 语言编写,使用 ORM 思想(Object Relational Mapping) 对象关系映射,就是把数据库表和实体类以及实体类的属性对应起来(实体类中的属性和数据库表的字段名称保持一致),让我们操作实体类就实现操作数据库表。
-
MyBatis 中实体类需要实现 Serializable 接口。
-
MyBatis 的环境搭建
- 创建 maven 工程并导入坐标
<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>
- 创建实体类(javaBean),和 dao 层的接口
- 创建 MyBatis 的主配置文件,名字没要求,在此 SqlMapConifg.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的主配置文件 --> <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://localhost:3306/eesy_mybatis"/> <property name="username" value="root"/> <property name="password" value="1234"/> </dataSource> </environment> </environments> <!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 --> <!-- 在注册映射文件时使用<mapper resource="org/xx/demo/mapper/xx.xml"/>,不需要映射文件名和接口名一样 --> <!-- 如果使用注解来配置,此处的 resources 应该用 class 来指定被注解的 dao 全限定类名 --> <mappers> <mapper resource="com/itheima/dao/IUserDao.xml"/> </mappers> </configuration>
- 创建映射配置文件
<?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.IUserDao"> <!--配置查询所有,id与接口中方法名一样--> <select id="findAll" resultType="com.itheima.domain.User"> select * from user </select> </mapper>
- 创建 maven 工程并导入坐标
-
环境搭建的注意事项
- 在 MyBatis 中持久层的操作接口名称也和映射文件也叫做 Mapper(以前为 dao)
- 在 resources 中创建目录的时候,和包不一样,需一级一级创建
- MyBatis 映射文件的位置必须和 dao 接口的包结构相同
- 映射配置文件的 mapper 标签 namespace 属性的取值必须是 dao 接口的全限定类名
- 映射配置文件的操作配置(select),id 属性的取值必须是 dao 接口的方法名
- 当我们遵循上述后 3 点之后,在开发中无需再写 dao 的实现类
-
入门案例
package com.itheima.test; import com.itheima.dao.IUserDao; import com.itheima.domain.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 java.io.InputStream; import java.util.List; public class MybatisTest { public static void main(String[] args)throws Exception { //1.读取配置文件 InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); //3.使用工厂生产SqlSession对象,操作数据库对象 SqlSession session = factory.openSession(); //4.使用SqlSession创建Dao接口的代理对象,代理模式 IUserDao userDao = session.getMapper(IUserDao.class); //5.使用代理对象执行方法 List<User> users = userDao.findAll(); for(User user : users){ System.out.println(user); } //6.释放资源 session.close(); in.close(); } }
-
使用注解的时候
- 不需要映射配置文件
- 主配置文件中
<mapper class="全限定类型"/>
- 接口上使用注解
@Select("select * from user")
-
MyBatis 支持写 dao 实现类,一般不用,不简便。
- 配置映射配置文件
- 写 dao 的实现类,通过读取配置文件的 SQL 语句来查询
public class UserDaoImpl implements IUserDao { private SqlSessionFactory factory; public UserDaoImpl(SqlSessionFactory factory) { this.factory = factory; } public List<User> findAll() { SqlSession session = factory.openSession(); List<User> users = session.selectList("com.itheima.dao.IUserDao.findAll"); session.close(); return users; } }
- 测试类
public static void main(String[] args) throws Exception { //1.读取配置文件 InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); //3.使用工厂创建dao对象 UserDaoImpl userDao = new UserDaoImpl(factory); //4.使用代理对象执行方法 List<User> users = userDao.findAll(); for (User user : users) { System.out.println(user); } //5.释放资源 in.close(); }
2. 自定义 MyBatis 框架
- MyBatis 在使用代理 dao 的方式实现增删改查时做了什么事情
- 创建代理对象
- 在代理对象中调用 selectList 方法
- 执行查询所有分析
- 创建代理对象的分析
- 自定义 MyBatis 开发流程图
3. MyBatis 的 CRUD
-
CRUD 使用要求
- 持久层接口和持久层接口的映射配置必须在相同的包下
- 持久层映射配置中 mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名
- SQL 语句的配置标签
<select>,<insert>,<delete>,<update>
的 id 属性必须和持久层接口的方法名相同。
-
MyBatis 动态执行 SQL。
-
MyBatis 默认手动提交事务,需要
sqlSession.commit();
-
insert 操作:
<insert id="saveUser" parameterType="pers.ylq.domain.User"> <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER"> select last_insert_id(); </selectKey> insert into user(username,address,sex,birthday) values(#{username},#{address},#{sex},#{birthday}); </insert>
- parameterType 属性:用于指定传入参数的类型,全类名。
- resultType 属性:用于指定结果集的类型(查询的每条结果要封装的类型)。简单数据类型可以随意写,能识别就好,比如 int、Integer、String、java.liang.String ,如果结果是一个类,则需要写全类名。
- SQL 语句中使用
#{}
字符 :- 它代表占位符,相当于原来 JDBC 部分所学的
?
,都是用于执行语句时替换实际的数据。具体的数据是由#{}
里面的内容决定的。 - 语法格式就是使用
#{对象.属性}
的方式,#{user.username}
它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用getUsername()
方法把值取出来。但是我们在 parameterType 属性上指定了实体类名称,所以可以省略user.
而直接写 username。
- 它代表占位符,相当于原来 JDBC 部分所学的
- selectKey 的作用:配置保存时获取插入的 id,id 为自增长,keyProperty 表示返回给对象的哪一个属性,keyColumn 指向表中的主键,可省略,order 表示在什么时候执行。
select last_insert_id()
查询上一条自增长数据的 id。
-
update 操作
<update id="updateUser" parameterType="pers.ylq.domain.User"> update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}; </update>
#{}
中的值代表 User 对象中的属性。
-
delete 操作
<delete id="deleteUser" parameterType="int"> delete from user where id=#{id}; </delete>
- 由于参数类型是基本类型,
#{}
中内容可随意填写。
- 由于参数类型是基本类型,
-
select 操作:
List<User> findAll(); <select id="findAll" resultType="pers.ylq.domain.User"> select * from user; </select>
- resultType 为查询的结果封装的类型,不是方法的返回值类型。
-
模糊查询:
List<User> findByName(String username); <select id="findByName" parameterType="String" resultType="pers.ylq.domain.User"> select * from user where username like #{name}; </select> List<User> users = userDao.findByName("%王%");
- 在传递参数的时候传递
%
可实现动态模糊查询
<select id="findByName" parameterType="String" resultType="pers.ylq.domain.User"> select * from user where username like concat('%',#{name},'%'); </select> List<User> users = userDao.findByName("王");
- 使用 concat 函数连接参数。
List<User> users = userDao.findByName("王"); <select id="findByName" parameterType="String" resultType="pers.ylq.domain.User"> select * from user where username like '%${value}%'; </select>
- 通过
${}
可以将 parameterType 传入的内容拼接在 SQL 中且不进行 JDBC 类型转换,${}
可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值 ,${}
括号中只能是 value。
public class QueryVo implements Serializable { private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } } <select id="findUserByVo" resultType="pers.ylq.domain.User" parameterType="pers.ylq.domain.QueryVo"> select * from user where username like #{user.username}; </select> QueryVo vo = new QueryVo(); User user = new User(); vo.setUser(user); user.setUsername("%王%"); List<User> users = userDao.findUserByVo(vo);
- 查询的参数对象中存有对象
- 在传递参数的时候传递
-
MyBatis 传递多个参数
- 传递多个参数的时候,不能使用 parameterType 指定参数类型。
- 可以通过参数传递顺序取值
<select id="findByNameAndAddress" resultType="pers.ylq.domain.User"> select * from user where username = #{arg0} and address = #{arg1}; </select> List<User> users = userDao.findByNameAndAddress("老王", "北京");
- 在 dao 层通过注解 @Param 实现多参传递,注解中的属性为参数的名字,在 XML 中通过此名字获取参数。
List<User> findByNameAndAddress(@Param("username") String username, @Param("address") String address); <select id="findByNameAndAddress" resultType="pers.ylq.domain.User"> select * from user where username = #{username} and address = #{address}; </select> List<User> users = userDao.findByNameAndAddress("老王", "北京");
-
解决实体类属性和数据库列名不对应的两种方式
- 第一种:为查询出的数据用 as 关键字起别名,别名跟实体类中名字一样,即可成功封装为对象。
- 第二种:建立属性名和列名的对应关系,然后使用 resultMap 属性,指向此对应表即可
<!-- 配置 查询结果的列名和实体类的属性名的对应关系 --> <!-- id为对应表的唯一标识,可以随意起名 --> <resultMap id="userMap" type="pers.ylq.damain.User"> <!-- 主键字段的对应 --> <id property="userId" column="id"></id> <!--非主键字段的对应--> <result property="userName" column="username"></result> <result property="userAddress" column="address"></result> <result property="userSex" column="sex"></result> <result property="userBirthday" column="birthday"></result> </resultMap> <select id="findById" parameterType="INT" resultMap="userMap"> select * from user where id = #{uid} </select>
4. 主配置文件的配置和顺序
-
properties 标签:配置属性
-
<properties> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/eesy-mybatis"/> <property name="username" value="root"/> <property name="password" value="123456"/> </properties> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="url" value="${url}"/> <property name="driver" value="${driver}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments>
配置完成之后,可在下面通过
${name}
取值。 -
resources 属性:用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下。
<properties resource="jdbcConfig.properties"> </properties> <properties resource="pers/ylq/dao/jdbcConfig.properties"> </properties>
此文件若处于类的根路径下,因此直接写文件名就行。
下面依旧通过${name}
来取值。 -
url 属性
<properties url="file:///C:/Users/10766/Desktop/jdbcConfig.properties"> </properties>
URL:Uniform Resource Locator 统一资源定位符。它是可以唯一标识一个资源的位置。
它的写法:http://localhost:8080/mybatisserver/demo1Servlet 协议 主机 端口 URI
URI:Uniform Resource Identifier 统一资源标识符。它是在应用中可以唯一定位一个资源的。
-
-
settings 标签:对 MyBatis 进行一些设置,比如延迟加载。
-
typeAliases 标签:为类型起别名
<typeAliases> <typeAlias type="pers.ylq.domain.User" alias="user"></typeAlias> </typeAliases>
type 为实体类类型,alias 为别名,起了别名之后,不再区分大小写,在映射配置文件中可以用别名。
package 标签:用于指定要配置别名的包,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写。<typeAliases> <package name="pers.ylq.domain"></package> </typeAliases>
-
environments 标签(环境集合属性对象)
- environment(环境子属性对象)
- transactionManager(事务管理)
- dataSource(数据源)
- environment(环境子属性对象)
-
mappers 标签
- mapper:指定映射的接口或者 XML 文件,因为两者路径一样,指定哪个都行。
- resource:指定映射配置文件
- class:指定 mapper 接口类路径,此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
- package:用于指定 dao 接口所在的包,当指定了之后就不需要再写 mapper 以及 resource 或者 class 了。此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
- mapper:指定映射的接口或者 XML 文件,因为两者路径一样,指定哪个都行。
5. 执行过程分析(了解)
- 分析编写 dao 实现类 MyBatis 的执行过程
- 分析代理 dao 的执行过程
6. MyBatis 连接池及事务
- MyBatis 提供了 3 中连接池的配置方式
- 配置的位置:主配置文件的 DataSource 标签,type 属性。
- 取值:
- POOLED 采用传统的
javax.sql.DataSource
规范中的连接池,MyBatis 有针对规范的实现。 - UNPOOLED 采用传统的获取连接的方式,虽然也实现了 DataSource 接口,但并没有使用池的思想。
- JNDI 采用服务器提供的 JNDI 技术实现,来获取 DataSource 对象,不同服务器所能拿到的 DataSource 是不一样的。Tomcat 获取的连接池就是 dbcp 连接池。
注意:如果不是 Web 或者 maven 的 war 工程,是不能使用的。
- POOLED 采用传统的
- MyBatis 中事务通过 sqlsession 对象的 commit 方法和 rollback 方法实现事务的提交和回滚。
SqlSession openSession(boolean var1);
可通过设置参数为 true 来自动提交事务。
7. 动态 SQL
-
if 标签
<select id="findUserByCondition" parameterType="User" resultMap="userMap"> select * from user where 1 = 1 <if test="username!=null"> and username=#{username} </if> </select>
-
where 标签
<select id="findUserByCondition" parameterType="User" resultMap="userMap"> select * from user <where> <if test="username!=null"> and username=#{username} </if> <if test="sex!=null"> and sex=#{sex} </if> </where> </select>
-
foreach 标签
<select id="findUserByList" resultMap="userMap"> select * from user <where> <foreach collection="list" open="id in(" close=")" item="id" separator=","> #{id} </foreach> </where> </select>
- 如果传入的是单参数且参数类型是一个 List 的时候,collection 属性值为 list
- 如果传入的是单参数且参数类型是一个 array 数组的时候,collection 的属性值为 array
-
SQL 标签:声明 SQL 语句,下面可以直接引入
<sql id="defaultUser"> select * from user </sql> <select id="findAll" resultMap="userMap"> <include refid="defaultUser"></include> </select>
8. 多表查询
- 一对一查询(多对一查询)
-
有两个表,User 表和 Account 表,Account 表中有外键 uid 于 User 中 id 对应。
-
方式一:定义一个新的类,AccountUser extends Account,还包含用户的信息字段,将返回结果封装到此类中。
-
方式二:在 Account 类中加入 User 类的对象作为 Account 类的一个属性,定义一个 resultMap
<resultMap id="accountUserMap" type="Account"> <id property="id" column="aid"/> <result property="uid" column="uid"/> <result property="money" column="money"/> <association property="user" javaType="User"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="address" column="address"/> <result property="sex" column="sex"/> <result property="birthday" column="birthday"/> </association> </resultMap>
查询结果的 resultMap 指向此映射关系。
-
- 一对多查询:
- User 类中加入
List<Account> accounts;
<resultMap id="userAccountMap" type="User"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="address" column="address"/> <result property="sex" column="sex"/> <result property="birthday" column="birthday"/> <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 u.*,a.id aid,uid,money FROM USER u LEFT JOIN account a ON u.id=a.uid; </select>
- User 类中加入
- 多对多查询:在两个表的实体类中分别加入对方实体类的 List 集合,在各自配置文件配置 resultMap 查询即可。
9. MyBatis 延迟加载
- 延迟加载:需要数据的时候才加载,不需要不加载。
- 实现:先从单表查询,需要时再去从关联表关联查询。
- 主配置文件配置:
<settings> <!-- 延迟加载全局开关 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 开启时,任何方法调用都会加载该对象所有属性,否则,按需加载,默认false(true in<=3.4.1) --> <setting name="aggressiveLazyLoading" value="false"/> </settings>
- 实体类配置文件:
-
一对一实现延迟加载,association 实现
<resultMap id="accountUserMap" type="Account"> <id property="id" column="id"/> <result property="uid" column="uid"/> <result property="money" column="money"/> <association property="user" column="uid" javaType="User" select="pers.ylq.dao.IUserDao.findById"/> </resultMap> <select id="findAll" resultMap="accountUserMap"> select * from account </select>
select 标签声明延迟加载调用的方法,clomu 为所传递的参数,property 指向实体类中的成员变量
-
一对多实现延迟加载,collection 实现
<resultMap id="userMap" type="user"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="address" column="address"/> <result property="sex" column="sex"/> <result property="birthday" column="birthday"/> <collection property="accounts" ofType="Account" column="id" select="pers.ylq.dao.IAccountDao.findAccountById"/> </resultMap> <select id="findAll" resultMap="userMap"> select * from user </select>
-
10. 缓存
11. MyBatis 注解开发
-
在 MyBatis 中,CRUD 共 4 个注解
- @Select
- @Insert
- @Update
- @Delete
-
一个接口只能使用注解或 XML 文件,无法共存,即使用注解又使用 XML 会报错。即使 mapper 中使用 class 指向注解也会报错。
-
当实体类的成员变量名和数据库字段名不对应的时候,可以使用 @Results 来配置对应关系
@Select("select * from user") @Results(id = "userMap", value = { @Result(id = true, property = "userId", column = "id"), @Result(property = "userName", column = "username"), @Result(property = "userAddress", column = "address"), @Result(property = "userSex", column = "sex"), @Result(property = "userBirthday", column = "birthday"), }) List<User1> findAll();
- id 为该对应关系的唯一标识,供别的方法引用。
- @Result 中的 id=true,代表其为主键
- @Result 实现结果集的封装。
- 别的方法可通过 @ResultMap("id") 引用此映射关系
-
一对一查询配置(多对一)
-
MyBatis 中没有多对一的概念,多对一是通过一对一的方式来实现的。
-
首先在 Account 实体类中添加 User 变量,生成 getter 和 setter 方法。
-
在接口中使用注解配置 @Result
@Select("select * from account") @Results(id = "accountMap", value = { @Result(id = true, property = "id", column = "id"), @Result(property = "uid", column = "uid"), @Result(property = "money", column = "money"), @Result(property = "user",column = "uid",one = @One(select = "pers.ylq.dao.IUserDao.findById",fetchType = FetchType.EAGER)) }) List<Account> findAll();
- 最后一个 @Result 中,property 代表实体类中的变量,column 代表要传入的数据库的列名
- @Result 中有:
One one() default @One;
和Many many() default @Many;
,one 代表一对一,many 代表一对多,代表此对象对应几个对象。 - @One 中的属性 select 代表要执行的语句的唯一标识。
- fetchType 有三个取值:LAZY(延迟加载),EAGER(立即加载),DEFAULT(默认)。
-
-
一对多查询配置:
-
在 User 实体类中添加
private List<Account> accounts;
-
配置接口的注解
@Select("select * from user") @Results(id = "userMap", value = { @Result(id = true, property = "id", column = "id"), @Result(property = "username", column = "username"), @Result(property = "address", column = "address"), @Result(property = "sex", column = "sex"), @Result(property = "birthday", column = "birthday"), @Result(property = "accounts",column = "id", many = @Many(select = "pers.ylq.dao.IAccountDao.findAccountByUid", fetchType = FetchType.LAZY)) }) List<User> findAll();
- many 代表一对多的关系。
- FetchType.LAZY 代表延迟加载,此属性代替了在主配置文件中 settings 中的配置。
-
-
注解使用二级缓存:在 dao 层的接口上配置
@CacheNamespace(blocking=true)
-
常用注解说明:
- @Insert:实现新增
- @Update:实现更新
- @Delete:实现删除
- @Select:实现查询
- @Result:实现结果集封装
- @Results:可以与 @Result 一起使用,封装多个结果集
- @ResultMap:实现引用 @Results 定义的封装
- @One:实现一对一结果集封装
- @Many:实现一对多结果集封装
- @SelectProvider: 实现动态 SQL 映射
- @CacheNamespace:实现注解二级缓存的使用
-
@SelectProvider: 实现动态 SQL 映射
@SelectProvider(type = Provider.class,method = "selectProvider")
指定了一个类的 class 和类中的方法。- 此方法返回一个字符串,即为所要执行的 SQL 语句。
-
public class Provider { public String selectProvider(String name) { String sql = "select * from user where 1=1 "; if (name != null && !name.equals(" ")) { sql += " and username=#{name} "; } return sql; } }
12. mapper 层注解
- @Mapper, mybatis 注解
- @Repository, sping 注解
相同点:
@Mapper 和 @Repository 都是作用在 dao 层接口,使得其生成代理对象 bean,交给 spring 容器管理
不同点:
@Mapper 不需要配置扫描地址,可以单独使用,如果有多个mapper 文件的话,可以在项目启动类中加入 @MapperScan(“mapper文件所在包”),这样就不需要每个 mapper 文件都加 @Mapper 注解了。
@Repository 需要配置扫描地址
但在 idea 中,使用 @Repository 可以消除在业务层中注入 mapper 对象时的错误