框架概述

概念

mybatis是一个持久层框架,用java编写的;它封装了jdbc操作的很多细节,使开发者只需要关注sql语句本身,而无需关注注册驱动、创建连接等繁杂过程;它使用了ORM思想实现了结果集的封装。

ORM:

Object Relational Mappging 对象关系映射
就是把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表

@面:命名空间namespace的作用

在大型项目中,可能存在大量的SQL语句,这时候为每个SQL语句起一个唯一的标识就变得并不容易了。
为了解决这个问题,在MyBatis中,可以为每个映射文件起一个唯一的命名空间,这样定义在这个映射文件中的每个SQL语句就成了定义在这个命名空间中的一个ID。只要我们能够保证每个命名空间中这个ID是唯一的,即使在不同映射文件中的语句ID相同,也不会再产生冲突了。

环境搭建&入门案例

创建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>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.10</version>
    </dependency>
</dependencies>

创建实体类和dao的接口

public class User implements Serializable{
   
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    public Integer getId() {
   
        return id;
    }

    public void setId(Integer id) {
   
        this.id = id;
    }

    public String getUsername() {
   
        return username;
    }

    public void setUsername(String username) {
   
        this.username = username;
    }

    public Date getBirthday() {
   
        return birthday;
    }

    public void setBirthday(Date birthday) {
   
        this.birthday = birthday;
    }

    public String getSex() {
   
        return sex;
    }

    public void setSex(String sex) {
   
        this.sex = sex;
    }

    public String getAddress() {
   
        return address;
    }

    public void setAddress(String address) {
   
        this.address = address;
    }

    @Override
    public String toString() {
   
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
public interface IUserDao {
   
    /** * 查询所有操作 * @return */
    List<User> findAll();
}

创建Mybatis的主配置文件

SqlMapConifg.xml

<!-- 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独立的配置文件 -->
    <mappers>
        <mapper resource="com/itheima/dao/IUserDao.xml"/>
    </mappers>
</configuration>

创建映射配置文件

IUserDao.xml

<mapper namespace="com.itheima.dao.IUserDao">
    <!--配置查询所有-->
    <select id="findAll" resultType="com.itheima.domain.User">
        select * from user
    </select>
</mapper>

注意事项:
	不要忘记在映射配置中告知mybatis要封装到哪个实体类中
	配置的方式:指定实体类的全限定类名

要求:
创建位置:必须和持久层接口在相同的包中。
名称:必须以持久层接口名称命名文件名,扩展名是.xml

编写测试类

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();
}

环境搭建注意事项

  • 第一个:创建IUserDao.xml 和 IUserDao.java时名称是为了和我们之前的知识保持一致。在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper。所以IUserDao 和 IUserMapper是一样的
  • 第二个:在idea中创建目录的时候,它和包是不一样的。包在创建时:com.itheima.dao它是三级结构。目录在创建时:com.itheima.dao是一级目录
  • 第三个:mybatis的映射配置文件位置必须和dao接口的包结构相同
  • 第四个:映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
  • 第五个:映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名

当我们遵从了第三,四,五点之后,我们在开发中就无须再写dao的实现类。

基于注解的mybatis

1、在持久层接口中添加注解

public interface IUserDao {
   
    /** * 查询所有操作 * @return */
    @Select("select * from user")
    List<User> findAll();
}

2、修改 SqlMapConfig.xml

<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 如果是用注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名 -->
<mappers>
    <mapper class="com.itheima.dao.IUserDao"/>
</mappers>

在使用基于注解的Mybatis配置时,请移除xml的映射配置(IUserDao.xml)。

设计模式分析

自定义Mybatis框架(以后看)

分析流程

引入相关坐标

代理Dao实现CRUD操作

增加操作

1.在持久层接口中添加新增方法

public interface IUserDao {
   
    /** * 保存用户 * @param user */
    void saveUser(User user);
}

2.在用户的映射配置文件中配置

<!-- 保存用户--> 
<insert id="saveUser" parameterType="com.itheima.domain.User">
    insert into user(username,birthday,sex,address) 
    	values(#{username},#{birthday},#{sex},#{address})
</insert>
parameterType 属性:
代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。

sql语句中使用#{}字符: 
它代表占位符,相当于原来jdbc部分所学的,都是用于执行语句时替换实际的数据。具体的数据是由#{}里面的内容决定的。
#{}中内容写法:我们保存方法的参数是一个User对象,此处要写User对象中的属性名称。它用的是ognl表达式。

#{user.username}
它会先去找user对象,然后在user对象中找到username属性,并调用getUsername()方法把值取出来。但是我们在parameterType属性上指定了实体类名称,所以可以省略user,而直接写username。

ognl 表达式:

它是apache提供的一种表达式语言,全称是:Object Graphic Navigation Language对象图导航语言
它是按照一定的语法格式来获取数据的。语法格式就是使用 #{对象.c}的方式

它是通过对象的取值方法来获取数据。在写法上把get给省略了。
比如:我们获取用户的名称时。类中的写法:user.getUsername();OGNL表达式写法:user.username
mybatis中为什么能直接写username,而不用user.呢?因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名

3.测试方法

public class MybatisTest {
   
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before//用于在测试方法执行之前执行
    public void init()throws Exception{
   
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        sqlSession = factory.openSession();
        //4.获取dao的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);
    }

    @After//用于在测试方法执行之后执行
    public void destroy()throws Exception{
   
        //提交事务
        sqlSession.commit();
        //6.释放资源
        sqlSession.close();
        in.close();
    }

    /** * 测试保存操作 */
    @Test
    public void testSave(){
   
        User user = new User();
        user.setUserName("modify User property");
        user.setUserAddress("北京市顺义区");
        user.setUserSex("男");
        user.setUserBirthday(new Date());
        System.out.println("保存操作之前:"+user);
        //5.执行保存方法
        userDao.saveUser(user);

        System.out.println("保存操作之后:"+user);
    }
}

4.扩展:新增用户 id 的返回值

新增用户后,同时还要返回当前新增用户的 id 值,因为 id 是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长auto_increment的值返回。

<insert id="saveUser" parameterType="USER">
    <!-- 配置保存时获取插入的 id --> 
    <selectKey keyColumn="id" keyProperty="id" resultType="int">
    	select last_insert_id();
    </selectKey>
    insert into user(username,birthday,sex,address) 
    values(#{username},#{birthday},#{sex},#{address})
</insert>

删除操作

<!-- 删除用户 --> 
<delete id="deleteUser" parameterType="java.lang.Integer">
	delete from user where id = #{uid}
</delete>

修改操作

<!-- 更新用户 --> 
<update id="updateUser" parameterType="com.itheima.domain.User">
    update user set username=#{username},birthday=#{birthday},sex=#{sex},
    	address=#{address} where id=#{id}
</update>

查询操作

1.根据 ID 查询

<!-- 根据 id 查询 --> 
<select id="findById" resultType="com.itheima.domain.User" parameterType="int">
	select * from user where id = #{uid}
</select>

resultType 属性:用于指定结果集的类型。
parameterType 属性:用于指定传入参数的类型。

2.模糊查询

1)在持久层接口中添加模糊查询方法

/** * 根据名称模糊查询 * @param username * @return */
List<User> findByName(String username);

2)在用户的映射配置文件中配置

<!-- 根据名称模糊查询 --> 
<select id="findByName" resultType="com.itheima.domain.User" parameterType="String">
 	select * from user where username like #{username}
</select>

3)测试方法

@Test
public void testFindByName(){
   
    //5.执行查询一个方法
     List<User> users = userDao.findByName("%王%");
     for(User user : users){
   
     	System.out.println(user);
     }
 }

我们在配置文件中没有加入%来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标识%

配置文件中的#{username}也只是一个占位符,所以 SQL 语句显示为“?”

4)另一种配置方式

第一步:修改 SQL 语句的配置,配置如下:

<!-- 根据名称模糊查询 --> 
<select id="findByName" parameterType="string" resultType="com.itheima.domain.User">
 	select * from user where username like '%${value}%'
</select>

我们在上面将原来的#{}占位符,改成了${value}。注意如果用模糊查询的这种写法,那么${value}的写法就是固定的,不能写成其它名字。

@面:#{}与${}的区别

#{}是预编译处理,${}是字符串替换。
1)mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值。
2)mybatis在处理${}时,就是把${}替换成变量的值。
3)#{}、${}可以接收简单类型值或实体类类型属性值。如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称;${}括号中只能是value。
4)使用#{}可以有效的防止SQL注入,提高系统安全性。
原因在于:预编译机制。预编译完成之后,SQL的结构已经固定,即便用户输入非法参数,也不会对SQL的结构产生影响,从而避免了潜在的安全风险。
5)预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。
我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。

输入参数封装

parameterType配置参数

1.使用说明

已经介绍了SQL语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型、引用类型,还可以是实体类类型(POJO 类),同时也可以使用实体类的包装类。

将介绍如何使用实体类的包装类作为参数传递。

2.注意事项

基本类型和String可以直接写类型名称 ,也可以使用包名.类名的方式 ,例如:java.lang.String。

实体类类型,目前我们只能使用全限定类名,原因是mybaits在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的实体类并没有注册别名,所以必须写全限定类名。

传递pojo包装对象

开发中通过pojo传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。

1.编写 QueryVo

public class QueryVo implements Serializable {
   
    private User user;
    
    public User getUser() {
   
    	return user; 
    }
    public void setUser(User user) {
   
        this.user = user; 
    } 
}

2.编写持久层接口

public interface IUserDao {
   
    /** * 根据 QueryVo 中的条件查询用户 * @param vo * @return */
    List<User> findByVo(QueryVo vo);
}

3.持久层接口的映射文件

<!-- 根据用户名称模糊查询,参数变成一个QueryVo对象了 --> 
<select id="findByVo" resultType="com.itheima.domain.User" parameterType="com.itheima.domain.QueryVo">
	select * from user where username like #{user.username};
</select>

4.测试包装类作为参数

@Test
public void testFindByQueryVo() {
   
    QueryVo vo = new QueryVo();
    User user = new User();
    user.setUserName("%王%");
    vo.setUser(user);
    List<User> users = userDao.findByVo(vo);
    for(User u : users) {
   
    	System.out.println(u);
    } 
}

输出结果封装

resultType配置结果类型

**实体类中的属性名称必须和查询语句中的列名保持一致,**否则无法实现封装。

1.基本类型

1)Dao 接口

/** * 查询总记录条数 * @return */
int findTotal();

2)映射配置

<!-- 查询总记录条数 --> 
<select id="findTotal" resultType="int">
	select count(*) from user;
</select>

2.实体类类型

1)Dao 接口

/** * 查询所有用户 * @return */
List<User> findAll();

2)映射配置

<!-- 配置查询所有操作 --> 
<select id="findAll" resultType="com.itheima.domain.User">
	select * from user
</select>

3.特殊情况

此时的实体类属性和数据库表的列名已经不一致

1)Dao 接口

/** * 查询所有用户 * @return */
List<User> findAll();

2)修改映射配置

使用别名查询,其他方法见下面。
<!-- 配置查询所有操作 --> 
<select id="findAll" resultType="com.itheima.domain.User">
	select id as userId,username as userName,birthday as userBirthday,
		sex as userSex,address as userAddress from user
</select>

resultMap结果类型

**resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系,**从而实现封装。

在 select 标签中使用 resultMap 属性指定引用即可,同时resultMap可以实现将查询结果映射为复杂类型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。

1.定义 resultMap

<!-- 建立User实体和数据库表的对应关系 type属性:指定实体类的全限定类名 id属性:给定一个唯一标识,是给查询select标签引用用的。 --> 
<resultMap type="com.itheima.domain.User" id="userMap"> 
    <id column="id" property="userId"/>
    <result column="username" property="userName"/>
    <result column="sex" property="userSex"/>
    <result column="address" property="userAddress"/>
    <result column="birthday" property="userBirthday"/>
</resultMap>

id 标签:用于指定主键字段
result 标签:用于指定非主键字段
column 属性:用于指定数据库列名
property 属性:用于指定实体类属性名称

2.映射配置

<!-- 配置查询所有操作 --> 
<select id="findAll" resultMap="userMap">
	select * from user
</select>

3.测试结果

@Test
public void testFindAll() {
   
    List<User> users = userDao.findAll();
    for(User user : users) {
   
    	System.out.println(user);
    } 
}

传统 DAO 层开发

使用 Mybatis 开发 Dao,通常有两个方法,即原始 Dao 开发方式Mapper接口代理开发方式

CRUD操作

1.持久层 Dao 接口

public interface IUserDao {
   

    /** * 查询所有用户 * @return */
    List<User> findAll();

    /** * 保存用户 * @param user */
    void saveUser(User user);

    /** * 更新用户 * @param user */
    void updateUser(User user);

    /** * 根据Id删除用户 * @param userId */
    void deleteUser(Integer userId);

    /** * 根据id查询用户信息 * @param userId * @return */
    User findById(Integer userId);

    /** * 根据名称模糊查询用户信息 * @param username * @return */
    List<User> findByName(String username);

    /** * 查询总用户数 * @return */
    int findTotal();
}

2.持久层 Dao 实现类

public class UserDaoImpl implements IUserDao {
   

    private SqlSessionFactory factory;

    public UserDaoImpl(SqlSessionFactory factory){
   
        this.factory = factory;
    }

    @Override
    public List<User> findAll() {
   
        //1.根据factory获取SqlSession对象
        SqlSession session = factory.openSession();
        //2.调用SqlSession中的方法,实现查询列表
        //参数就是能获取配置信息的key
        List<User> users = session.selectList("com.itheima.dao.IUserDao.findAll");
        //3.释放资源
        session.close();
        return users;
    }

    @Override
    public void saveUser(User user) {
   
        //1.根据factory获取SqlSession对象
        SqlSession session = factory.openSession();
        //2.调用方法实现保存
        session.insert("com.itheima.dao.IUserDao.saveUser",user);
        //3.提交事务
        session.commit();
        //4.释放资源
        session.close();
    }

    @Override
    public void updateUser(User user) {
   
        //1.根据factory获取SqlSession对象
        SqlSession session = factory.openSession();
        //2.调用方法实现更新
        session.update("com.itheima.dao.IUserDao.updateUser",user);
        //3.提交事务
        session.commit();
        //4.释放资源
        session.close();
    }

    @Override
    public void deleteUser(Integer userId) {
   
        //1.根据factory获取SqlSession对象
        SqlSession session = factory.openSession();
        //2.调用方法实现更新
        session.update("com.itheima.dao.IUserDao.deleteUser",userId);
        //3.提交事务
        session.commit();
        //4.释放资源
        session.close();
    }

    @Override
    public User findById(Integer userId) {
   
        //1.根据factory获取SqlSession对象
        SqlSession session = factory.openSession();
        //2.调用SqlSession中的方法,实现查询一个
        User user = session.selectOne("com.itheima.dao.IUserDao.findById",userId);
        //3.释放资源
        session.close();
        return user;
    }

    @Override
    public List<User> findByName(String username) {
   
        //1.根据factory获取SqlSession对象
        SqlSession session = factory.openSession();
        //2.调用SqlSession中的方法,实现查询列表
        List<User> users = session.selectList("com.itheima.dao.IUserDao.findByName",username);
        //3.释放资源
        session.close();
        return users;
    }

    @Override
    public int findTotal() {
   
        //1.根据factory获取SqlSession对象
        SqlSession session = factory.openSession();
        //2.调用SqlSession中的方法,实现查询一个
        Integer count = session.selectOne("com.itheima.dao.IUserDao.findTotal");
        //3.释放资源
        session.close();
        return count;
    }
}

3.持久层映射配置

<mapper namespace="com.itheima.dao.IUserDao">
    <!-- 查询所有 -->
    <select id="findAll" resultType="com.itheima.domain.User">
        select * from user;
    </select>

    <!-- 保存用户 -->
    <insert id="saveUser" parameterType="com.itheima.domain.User">
        <!-- 配置插入操作后,获取插入数据的id -->
        <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>

    <!-- 更新用户 -->
    <update id="updateUser" parameterType="com.itheima.domain.User">
        update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}
    </update>

    <!-- 删除用户-->
    <delete id="deleteUser" parameterType="java.lang.Integer">
        delete from user where id = #{uid}
    </delete>
    
    <!-- 根据id查询用户 -->
    <select id="findById" parameterType="INT" resultType="com.itheima.domain.User">
        select * from user where id = #{uid}
    </select>

    <!-- 根据名称模糊查询 -->
    <select id="findByName" parameterType="string" resultType="com.itheima.domain.User">
          select * from user where username like #{name}
   </select>

    <!-- 获取用户的总记录条数 -->
    <select id="findTotal" resultType="int">
        select count(id) from user;
    </select>
</mapper>

4.测试类

public class MybatisTest {
   

    private InputStream in;
    private IUserDao userDao;

    @Before//用于在测试方法执行之前执行
    public void init() throws Exception{
   
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.使用工厂对象,创建dao对象
        userDao = new UserDaoImpl(factory);
    }

    @After//用于在测试方法执行之后执行
    public void destroy()throws Exception{
   
        //6.释放资源
        in.close();
    }

    /** * 测试查询所有 */
    @Test
    public void testFindAll(){
   
        //5.执行查询所有方法
        List<User> users = userDao.findAll();
        for(User user : users){
   
            System.out.println(user);
        }
    }
    /** * 测试保存操作 */
    @Test
    public void testSave(){
   
        User user = new User();
        user.setUsername("dao impl user");
        user.setAddress("北京市顺义区");
        user.setSex("男");
        user.setBirthday(new Date());
        System.out.println("保存操作之前:"+user);
        //5.执行保存方法
        userDao.saveUser(user);

        System.out.println("保存操作之后:"+user);
    }

    /** * 测试更新操作 */
    @Test
    public void testUpdate(){
   
        User user = new User();
        user.setId(50);
        user.setUsername("userdaoimpl update user");
        user.setAddress("北京市顺义区");
        user.setSex("女");
        user.setBirthday(new Date());

        //5.执行保存方法
        userDao.updateUser(user);
    }

    /** * 测试删除操作 */
    @Test
    public void testDelete(){
   
        //5.执行删除方法
        userDao.deleteUser(54);
    }

    /** * 测试删除操作 */
    @Test
    public void testFindOne(){
   
        //5.执行查询一个方法
        User  user = userDao.findById(50);
        System.out.println(user);
    }

    /** * 测试模糊查询操作 */
    @Test
    public void testFindByName(){
   
        //5.执行查询一个方法
        List<User> users = userDao.findByName("%王%");
        for(User user : users){
   
            System.out.println(user);
        }
    }

    /** * 测试查询总记录条数 */
    @Test
    public void testFindTotal(){
   
        //5.执行查询一个方法
        int count = userDao.findTotal();
        System.out.println(count);
    }
}

SqlMapConfig.xml配置文件

配置内容

properties属性

在使用 properties 标签配置时,我们可以采用两种方式指定属性配置。

1.第一种

<properties> 
    <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
	<property name="jdbc.url" value="jdbc:mysql://localhost:3306/eesy"/>
    <property name="jdbc.username" value="root"/>
	<property name="jdbc.password" value="1234"/>
</properties>

2.第二种

1)在 classpath 下定义 db.properties 文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234

2)properties 标签配置

<!-- 配置连接数据库的信息 resource属性:用于指定 properties 配置文件的位置,要求配置文件必须在类路径下 resource="jdbcConfig.properties" url属性: URL:Uniform Resource Locator 统一资源定位符 http://localhost:8080/mystroe/CategoryServlet URL 协议 主机 端口 URI URI:Uniform Resource Identifier 统一资源标识符 /mystroe/CategoryServlet 它是可以在 web 应用中唯一定位一个资源的路径 --> 

<properties url= file:///D:/IdeaProjects/day02_eesy_01mybatisCRUD/src/main/resources/jdbcConfig.prop erties">
</properties>
<dataSource type="POOLED"> 
    <property name="driver" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</dataSource>

typeAliases(类型别名)

<typeAliases>
	<!-- 单个别名定义 --> 
    <typeAlias alias="user" type="com.itheima.domain.User"/>
    
	<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) --> 
    <package name="com.itheima.domain"/>
</typeAliases>

mappers(映射器)

1.< mapper resource=" " />

使用相对于类路径的资源

如:< mapper resource="com/itheima/dao/IUserDao.xml" />

2.< mapper class=" " />

使用 mapper 接口类路径

如:< mapper class="com.itheima.dao.UserDao"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。

3.< package name=""/>

注册指定包下的所有 mapper 接口

如:< package name="cn.itcast.mybatis.mapper"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。

连接池与事务深入

连接池技术

在 Mybatis 中也有连接池技术,但是它采用的是自己的连接池技术。在 Mybatis 的 SqlMapConfig.xml 配置文件中,通过< dataSource type="pooled">来实现 Mybatis 中连接池的配置。

1.连接池的分类

  • POOLED

采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现

  • UNPOOLED

采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想。

  • JNDI

采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样。

注意:如果不是web或者maven的war工程,是不能使用的。我们课程中使用的是tomcat服务器,采用连接池就是dbcp连接池。

2.数据源的配置

我们的数据源配置就是在SqlMapConfig.xml文件中,具体配置如下:
<!-- 配置数据源(连接池)信息 --> 
<dataSource type="POOLED"> 
    <property name="driver" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</dataSource>

MyBatis 在初始化时,根据<dataSource>的type属性来创建相应类型的的数据源DataSource,即:
type=”POOLED”:MyBatis 会创建 PooledDataSource 实例
type=”UNPOOLED” : MyBatis 会创建 UnpooledDataSource 实例
type=”JNDI”:MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用

事务控制

自动提交事务的设置

为什么 CUD 过程中必须使用 sqlSession.commit()提交事务?

主要原因就是在连接池中取出的连接,都会将调用 connection.setAutoCommit(false)方法,这样我们就必须使用 sqlSession.commit()方法,相当于使用了 JDBC 中的 connection.commit()方法实现事务提交。

动态 SQL 语句

< if>标签

1)持久层 Dao 接口

/** * 根据用户信息,查询用户列表 * @param user * @return */
List<User> findByUser(User user);

2)持久层 Dao 映射配置

<select id="findByUser" resultType="user" parameterType="user">
    select * from user where 1=1
    <if test="username!=null and username != '' ">
    	and username like #{username}
    </if> 
    <if test="address != null">
    	and address like #{address}
    </if>
</select>

注意:<if>标签的test属性中写的是对象的属性名,如果是包装类的对象要使用OGNL表达式的写法。

3)测试

@Test
public void testFindByUser() {
   
    User u = new User();
    u.setUsername("%王%");
    u.setAddress("%顺义%");
    //6.执行操作
    List<User> users = userDao.findByUser(u);
    for(User user : users) {
   
    	System.out.println(user);
	} 
}

< where>标签

<!-- 根据用户信息查询 --> 
<select id="findByUser" resultType="user" parameterType="user"> 
    <include refid="defaultSql"></include> 
    <where> 
        <if test="username!=null and username != '' ">
			and username like #{username}
		</if> 
        <if test="address != null">
			and address like #{address}
		</if>
	</where>
</select>

< foreach>标签

传入多个 id 查询用户信息,用下边两个 sql 实现:
SELECT * FROM USERS WHERE username LIKE ‘%张%’ AND (id =10 OR id =89 OR id=16)
SELECT * FROM USERS WHERE username LIKE ‘%张%’ AND id IN (10,89,16)
这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来

1.加入一个 List 集合用于封装参数

public class QueryVo implements Serializable {
   
	private List<Integer> ids;

    public List<Integer> getIds() {
   
    	return ids; 
    }
    public void setIds(List<Integer> ids) {
   
    	this.ids = ids; 
    }
}

2.持久层 Dao 接口

/** * 根据 id 集合查询用户 * @param vo * @return */
List<User> findInIds(QueryVo vo);

3.持久层 Dao 映射配置

<!-- 查询所有用户在 id 的集合之中 --> 
<select id="findInIds" resultType="user" parameterType="queryvo">
    select * from user where id in (1,2,3,4,5);
    <where> 
        <if test="ids != null and ids.size() > 0"> 
            <foreach collection="ids" open="id in ( " close=")" item="uid" separator=",">
    			#{uid}
    		</foreach>
    	</if>
    </where>
</select>

SQL 语句:
select 字段 from user where id in (?)
<foreach>标签用于遍历集合,它的属性:
    collection:代表要遍历的集合元素,注意编写时不要写#{}
    open:代表语句的开始部分
    close:代表结束部分
    item:代表遍历集合的每个元素,生成的变量名
    sperator:代表分隔符

简化编写的 SQL 片段

1.定义代码片段

<!-- 抽取重复的语句代码片段 --> 
<sql id="defaultSql">
	select * from user
</sql>

2.引用代码片段

<!-- 配置查询所有操作 --> 
<select id="findAll" resultType="user"> 
    <include refid="defaultSql"></include>
</select>

<!-- 根据 id 查询 --> 
<select id="findById" resultType="UsEr" parameterType="int">
    <include refid="defaultSql"></include>
		where id = #{uid}
</select>

@面:MyBatis中的动态SQL是什么

对于一些复杂的查询,我们可能会指定多个查询条件,但是这些条件可能存在也可能不存在,需要根据用户指定的条件动态生成SQL语句。如果不使用持久层框架我们可能需要自己拼装SQL语句,MyBatis提供了动态SQL的功能来解决这个问题。
MyBatis中用于实现动态SQL的元素主要有:
- if
- choose / when / otherwise
- trim
- where
- set
- foreach

多表查询

以最为简单的用户和账户的模型来分析 Mybatis 多表关系。用户为 User 表,账户为Account表。一个用户可以有多个账户。

一对一查询

需求:查询所有账户信息,关联查询下单用户信息。
注意:因为一个账户信息只能供某个用户使用,所以从查询账户信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的账户信息则为一对多查询,因为一个用户可以有多个账户。

1.方式一:

1)定义账户信息的实体类

public class Account implements Serializable {
   
    private Integer id;
    private Integer uid;
    private Double money;

    public Integer getId() {
   
		return id; 
    }
    public void setId(Integer id) {
   
    	this.id = id; 
    }
    public Integer getUid() {
   
    	return uid; 
    }
    public void setUid(Integer uid) {
   
    	this.uid = uid; 
    }
    public Double getMoney() {
   
    	return money; 
    }
    public void setMoney(Double money) {
   
    	this.money = money; 
    }
    @Override
    public String toString() {
   
    	return "Account [id=" + id + ", uid=" + uid + ", money=" + money + "]"; 
    } 
}

2)编写 Sql 语句

实现查询账户信息时,也要查询账户所对应的用户信息。

SELECT  account.*,user.username,user.address
FROM account,user
WHERE account.uid = user.id

3)定义 AccountUser 类

public class AccountUser extends Account implements Serializable {
   
    private String username;
    private String address;
    public String getUsername() {
   
    	return username; 
    }
    public void setUsername(String username) {
   
    	this.username = username; 
    }
    public String getAddress() {
   
    	return address; 
    }
    public void setAddress(String address) {
   
    	this.address = address; 
    }
    @Override
    public String toString() {
   
        return super.toString() + " AccountUser [username=" + username + ", 
        address=" + address + "]"; 
    } 
}

4)定义账户的持久层 Dao 接口

public interface IAccountDao {
   
    /** * 查询所有账户,同时获取账户的所属用户名称以及它的地址信息 * @return */
    List<AccountUser> findAll();
}

5)定义 AccountDao.xml 文件中的查询配置信息

<mapper namespace="com.itheima.dao.IAccountDao">
    <!-- 配置查询所有操作--> 
    <select id="findAll" resultType="accountuser">
    	select a.*,u.username,u.address 
        from account a,user u 
        where a.uid =u.id;
    </select> 
</mapper>

注意:因为上面查询的结果中包含了账户信息同时还包含了用户信息,所以我们的返回值类型returnType的值设置为AccountUser类型,这样就可以接收账户信息和用户信息了。

2.方式二

**使用 resultMap,**定义专门的 resultMap 用于映射一对一查询结果。

通过面向对象的(has a)关系可以得知,我们可以在 Account 类中加入一个 User 类的对象来代表这个账户是哪个用户的。

1)修改 Account 类

在 Account 类中加入 User 类的对象作为 Account 类的一个属性。

2)修改 AccountDao 接口中的方法

​ List < Account> findAll();

3) 重新定义 AccountDao.xml 文件

<mapper namespace="com.itheima.dao.IAccountDao">
    <!-- 建立对应关系 --> 
    <resultMap type="account" id="accountMap"> 
        <id column="aid" property="id"/>
    	<result column="uid" property="uid"/>
    	<result column="money" property="money"/>
        <!-- 它是用于指定从表方的引用实体属性的 --> 
        <association property="user" javaType="user"> 
            <id column="id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
            <result column="birthday" property="birthday"/>
            <result column="address" property="address"/>
        </association>
    </resultMap> 
    
    <select id="findAll" resultMap="accountMap">
    	select u.*,a.id as aid,a.uid,a.money from account a,user u where a.uid =u.id;
    </select>
</mapper>

一对多查询

需求:查询所有用户信息及用户关联的账户信息。
分析:
用户信息和他的账户信息为一对多关系,并且查询过程中如果用户没有账户信息,此时也要将用户信息查询出来,我们想到了左外连接查询比较合适。

1)编写 SQL 语句

SELECT u.*, acc.id id,acc.uid,acc.money
FROM user u
LEFT JOIN account acc ON u.id = acc.uid

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m67SHyht-1648185005280)(Pic/1647092326682.png)]

2)User 类加入 List< Account>

3)用户持久层 Dao 接口中加入查询方法

4)用户持久层 Dao 映射文件配置

<mapper namespace="com.itheima.dao.IUserDao"> 
    <resultMap type="user" id="userMap"> 
        <id column="id" property="id"></id> 
        <result column="username" property="username"/>
        <result column="address" property="address"/>
        <result column="sex" property="sex"/>
        <result column="birthday" property="birthday"/>
        
		<!-- collection 是用于建立一对多中集合属性的对应关系 ofType 用于指定集合元素的数据类型--> 
        <collection property="accounts" ofType="account"> 
            <id column="aid" property="id"/>
            <result column="uid" property="uid"/>
            <result column="money" property="money"/>
    	</collection>
	</resultMap>
    
    <!-- 配置查询所有操作 --> 
    <select id="findAll" resultMap="userMap">
		select u.*,a.id as aid ,a.uid,a.money from user u left outer join account 
			a on u.id =a.uid
	</select>
</mapper> 

collection
部分定义了用户关联的账户信息。表示关联查询结果集
property="accounts":
关联查询的结果集存储在User对象的上哪个属性。
ofType="account":
指定关联查询的结果集中的对象类型即List中的对象类型。此处可以使用别名,也可以使用全限定名。

多对多

多对多关系其实我们看成是双向的一对多关系。

实现 Role 到 User 多对多

1.用户与角色的关系模型

用户和角***r> 一个用户可以有多个角***r> 一个角色可以赋予多个用户
步骤:
1)建立两张表:用户表,角色表
让用户表和角色表具有多对多的关系。**需要使用中间表,中间表中包含各自的主键,**在中间表中是外键。
2)建立两个实体类:用户实体类和角色实体类
让用户和角色的实体类能体现出来多对多的关系,各自包含对方一个集合引用
3)建立两个配置文件
用户的配置文件
角色的配置文件
4)实现配置:
当我们查询用户时,可以同时得到用户所包含的角色信息
当我们查询角色时,可以同时得到角色的所赋予的用户信息

角色表:

用户角色中间表:

2.业务要求及实现 SQL

需求:
实现查询所有对象并且加载它所分配的用户信息。
分析:
查询角色我们需要用到Role表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中间表(USER_ROLE 表)才能关联到用户信息。
下面是实现的 SQL 语句:

SELECT
     r.*,
     u.id uid,
     u.username username,
     u.birthday birthday,
     u.sex sex,
     u.address address
FROM 
 	ROLE r
INNER JOIN USER_ROLE ur
ON ( r.id = ur.rid)
INNER JOIN USER u
ON (ur.uid = u.id);

3.编写角色实体类

public class Role implements Serializable {
   
     private Integer roleId;
     private String roleName;
	 private String roleDesc;
 	 //多对多的关系映射:一个角色可以赋予多个用户
 	 private List<User> users;
    
     public List<User> getUsers() {
   
     	return users;
     }
     public void setUsers(List<User> users) {
   
     	this.users = users;
     }
     public Integer getRoleId() {
   
     	return roleId;
     }
     public void setRoleId(Integer roleId) {
   
     	this.roleId = roleId;
     }
     public String getRoleName() {
   
     	return roleName;
     }
     public void setRoleName(String roleName) {
   
     	this.roleName = roleName;
     }
     public String getRoleDesc() {
   
     	return roleDesc;
     }
     public void setRoleDesc(String roleDesc) {
   
     	this.roleDesc = roleDesc;
     }
     @Override
     public String toString() {
   
     	return "Role{" +"roleId=" + roleId +", roleName='" + roleName + '\'' +", roleDesc='" + roleDesc + '\'' +'}';
     }
}

4.编写 Role 持久层接口

public interface IRoleDao {
   
     /** * 查询所有角色 * @return */
     List<Role> findAll();
}

5.编写映射文件

<mapper namespace="com.itheima.dao.IRoleDao">
 	 <!--定义 role 表的 ResultMap-->
 	 <resultMap id="roleMap" type="role">
         <id property="roleId" column="rid"></id>
         <result property="roleName" column="role_name"></result>
         <result property="roleDesc" column="role_desc"></result>
         <collection property="users" ofType="user">
             <id column="id" property="id"></id>
             <result column="username" property="username"></result>
             <result column="address" property="address"></result>
             <result column="sex" property="sex"></result>
             <result column="birthday" property="birthday"></result>
         </collection>
     </resultMap>
    
     <!--查询所有-->
     <select id="findAll" resultMap="roleMap">
     	select u.*,r.id as rid,r.role_name,r.role_desc from role r left outer join user_role ur on r.id = ur.rid left outer join user u on u.id = ur.uid
     </select>
</mapper>

实现 User 到 Role 的多对多

JNDI

延迟加载策略

问题:在一对多中,当我们有一个用户,它有100个账户。
在查询用户的时候,要不要把关联的账户查出来?在查询账户的时候,要不要把关联的用户查出来?

  • 在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询的。
  • 在查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来。

**延迟加载:**在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)

**立即加载:**不管用不用,只要一调用方法,马上发起查询。

在对应的四种表关系中:一对多,多对一,一对一,多对多

  • 一对多,多对多:通常情况下我们都是采用延迟加载。
  • 多对一,一对一:通常情况下我们都是采用立即加载。

assocation实现延迟加载

需求:查询账户信息同时查询用户信息。

1.账户的持久层 DAO 接口

public interface IAccountDao {
   
    /** * 查询所有账户,同时获取账户的所属用户名称以及它的地址信息 * @return */
    List<Account> findAll();
}

2.账户的持久层映射文件

<mapper namespace="com.itheima.dao.IAccountDao">
    <!-- 建立对应关系 --> 
    <resultMap type="account" id="accountMap"> 
        <id column="aid" property="id"/>
        <result column="uid" property="uid"/>
        <result column="money" property="money"/>
    	<!-- 它是用于指定从表的引用实体属性的 select:填写我们要调用的 select 映射的 id column :填写我们要传递给 select 映射的参数 --> 
        <association property="user" javaType="user" select="com.itheima.dao.IUserDao.findById" column="uid">
    	</association>
    </resultMap> 
    
    <select id="findAll" resultMap="accountMap">
    	select * from account
    </select>
</mapper>

3.用户的持久层接口和映射文件

public interface IUserDao {
   
    /** * 根据 id 查询 * @param userId * @return */
    User findById(Integer userId);
}
<mapper namespace="com.itheima.dao.IUserDao">
    <!-- 根据 id 查询 --> 
        <select id="findById" resultType="user" parameterType="int" >
    		select * from user where id = #{uid}
    	</select>
</mapper>

4.开启 Mybatis 的延迟加载策略

我们需要在 Mybatis 的配置文件 SqlMapConfig.xml 文件中添加延迟加载的配置。

<settings> 
    <setting name="lazyLoadingEnabled" value="true"/>
	<setting name="aggressiveLazyLoading" value="false"/>
</settings>

collection实现延迟加载

同样我们也可以在一对多关系配置的< collection>结点中配置延迟加载策略。< collection>结点中也有 select 属性,column 属性。
需求:
完成加载用户对象时,查询该用户所拥有的账户信息。

1.在 User 实体类中加入 List< Account>属性

2.编写用户和账户持久层接口的方法

3.编写用户持久层映射配置

<resultMap type="user" id="userMap"> 
    <id column="id" property="id"></id> 
    <result column="username" property="username"/>
    <result column="address" property="address"/>
    <result column="sex" property="sex"/>
    <result column="birthday" property="birthday"/>
    
    <!-- collection 是用于建立一对多中集合属性的对应关系 ofType 用于指定集合元素的数据类型 select 是用于指定查询账户的唯一标识(账户的dao全限定类名加上方法名称) column 是用于指定使用哪个字段的值作为条件查询 --> 
    <collection property="accounts" ofType="account" select="com.itheima.dao.IAccountDao.findByUid" column="id">
    </collection>
</resultMap>

<!-- 配置查询所有操作 --> 
<select id="findAll" resultMap="userMap">
	select * from user
</select>

<collection>标签:
主要用于加载关联的集合对象
select属性:
用于指定查询account列表的sql语句,所以填写的是该sql映射的id
column属性:
用于指定select属性的sql语句的参数来源,上面的参数来自于user的id列,所以就写成id这一个字段名了

4.编写账户持久层映射配置

<!-- 根据用户 id 查询账户信息 --> 
<select id="findByUid" resultType="account" parameterType="int">
	select * from account where uid = #{uid}
</select>

缓存

什么是缓存:存在于内存中的临时数据。
为什么使用缓存?减少和数据库的交互次数,提高执行效率。
适用于缓存:
经常查询并且不经常改变的。
数据的正确与否对最终结果影响不大的。
不适用于缓存:
经常改变的数据
数据的正确与否对最终结果影响很大的。
例如:商品的库存,银行的汇率,股市的牌价。

一级缓存

1.一级缓存的存在

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。

它指的是Mybatis中SqlSession对象的缓存。当我们执行查询之后,**查询的结果会同时存入到SqlSession为我们提供一块区域中,**该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中查询是否有,有的话直接拿出来用。当SqlSession对象消失时,mybatis的一级缓存也就消失了。

2.一级缓存的分析

一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。

  • 第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。
  • 得到用户信息,将用户信息存储到一级缓存中。
  • 如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
  • 第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。

3.一级缓存的清空

sqlSession.close()sqlSession.clearCache()

再次获取sqlSession并查询id=41的User对象时,又重新执行了sql 语句,从数据库进行了查询操作。

二级缓存

二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。

1.二级缓存概念:
它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。

2.二级缓存的开启与关闭

  • 第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
<settings>
<!-- 开启二级缓存的支持 --> 
    <setting name="cacheEnabled" value="true"/>
</settings>

因为cacheEnabled的取值默认就为true,所以这一步可以省略不配置。为true代表开启二级缓存;为false代表不开启二级缓存。
  • 第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
<mapper namespace="com.itheima.dao.IUserDao">
    <!-- 开启二级缓存的支持 -->
    <cache></cache>
</mapper>
  • 第三步:让当前的操作支持二级缓存(在select标签中配置)
<!-- 根据 id 查询 --> 
<select id="findById" resultType="user" parameterType="int" useCache="true">
	select * from user where id = #{uid}
</select> 

将UserDao.xml映射文件中的<select>标签中设置 useCache=”true”代表当前这个statement要使用二级缓存,如果不使用二级缓存可以设置为false。
注意:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。

3.注意事项

当我们在使用二级缓存时,所缓存的类一定要实现java.io.Serializable接口,这种就可以使用序列化方式来保存对象。

public class User implements Serializable {
   
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address; 
}

注解开发

注解开发中SqlMapConfig.xml依然存在,其他的xml用注解实现。

常用注解说明

@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询

@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
    
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
    
@CacheNamespace:实现注解二级缓存的使用

注解实现基本 CRUD

1.编写实体类

2.使用注解方式开发持久层接口

public interface IUserDao {
   
    /** * 查询所有用户 * @return */
    @Select("select * from user")
    @Results(id="userMap",
             value= {
   
                @Result(id=true,column="id",property="userId"),
                @Result(column="username",property="userName"),
                @Result(column="sex",property="userSex"),
                @Result(column="address",property="userAddress"),
                @Result(column="birthday",property="userBirthday")
   			 })
	List<User> findAll();
}

3.编写 SqlMapConfig 配置文件

<!-- 配置别名的注册 --> 
<typeAliases> 
    <package name="com.itheima.domain"/>
</typeAliases>

<!-- 配置映射信息 --> 
<mappers>
    <!-- 配置dao接口的位置,它有两种方式 第一种:使用mapper标签配置class属性 第二种:使用package标签,直接指定dao接口所在的包 --> 
    <package name="com.itheima.dao"/>
</mappers>

注解实现复杂关系映射

1.注解说明

@Results 注解
    代替的是标签<resultMap>
    该注解中可以使用单个@Result注解,也可以使用@Result集合
    @Results({@Result(),@Result()})或@Results(@Result())
    
@Resutl 注解
    代替了<id>标签和<result>标签
    @Result中属性介绍:
        id 是否是主键字段
        column 数据库的列名
        property 需要装配的属性名
        one 需要使用的@One 注解(@Result(one=@One)()))
        many 需要使用的@Many 注解(@Result(many=@many)()))
        
@One 注解(一对一)
    代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
    @One 注解属性介绍:
        select 指定用来多表查询的 sqlmapper
        fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。。
    使用格式:
    	@Result(column=" ",property="",one=@One(select=""))
        
@Many 注解(多对一)
    代替了<Collection>标签,是多表查询的关键,在注解中用来指定子查询返回对象集合。
    注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的javaType
    	(一般为 ArrayList)但是注解中可以不定义;
    使用格式:
    	@Result(property="",column="",many=@Many(select=""))

2.注解实现一对一复杂关系映射及延迟加载

1)添加 User 实体类及 Account 实体类

2)添加账户的持久层接口并使用注解配置

public interface IAccountDao {
   
    /** * 查询所有账户,采用延迟加载的方式查询账户的所属用户 * @return */
    @Select("select * from account")
    @Results(id="accountMap",
            value= {
   
                @Result(id=true,column="id",property="id"),
                @Result(column="uid",property="uid"),
                @Result(column="money",property="money"),
                @Result(column="uid",
                        property="user",
                        one=@One(select="com.itheima.dao.IUserDao.findById",
                        fetchType=FetchType.LAZY) )
            })
    List<Account> findAll();
}

3)添加用户的持久层接口并使用注解配置

public interface IUserDao {
   
    /** * 查询所有用户 * @return */
    @Select("select * from user")
    @Results(id="userMap",
            value= {
   
                @Result(id=true,column="id",property="userId"),
                @Result(column="username",property="userName"),
                @Result(column="sex",property="userSex"),
                @Result(column="address",property="userAddress"),
                @Result(column="birthday",property="userBirthday")
            })
    List<User> findAll();
    /** * 根据 id 查询一个用户 * @param userId * @return */
    @Select("select * from user where id = #{uid} ")
    @ResultMap("userMap")
    User findById(Integer userId);
}

3.注解实现一对多复杂关系映射

1)User 实体类加入 List

2)编写用户的持久层接口并使用注解配置

public interface IUserDao {
   
    /** * 查询所有用户 * @return */
    @Select("select * from user")
    @Results(id="userMap",
            value= {
   
                @Result(id=true,column="id",property="userId"),
                @Result(column="username",property="userName"),
                @Result(column="sex",property="userSex"),
                @Result(column="address",property="userAddress"),
                @Result(column="birthday",property="userBirthday"),
                @Result(column="id",property="accounts",
                        many=@Many(
                            select="com.itheima.dao.IAccountDao.findByUid",
                            fetchType=FetchType.LAZY
                        ) )
            })
    List<User> findAll();
}

@Many:
相当于<collection>的配置
select属性:代表将要执行的 sql 语句
fetchType属性:代表加载方式,一般如果要延迟加载都设置为 LAZY 的值

3)编写账户的持久层接口并使用注解配置

public interface IAccountDao {
   
    /** * 根据用户 id 查询用户下的所有账户 * @param userId * @return */
    @Select("select * from account where uid = #{uid} ")
    List<Account> findByUid(Integer userId);
}

基于注解的二级缓存

1.在 SqlMapConfig 中开启二级缓存支持

<!-- 配置二级缓存 --> 
<settings>
	<!-- 开启二级缓存的支持 --> 
    <setting name="cacheEnabled" value="true"/>
</settings>

2.在持久层接口中使用注解配置二级缓存

@CacheNamespace(blocking=true)//mybatis基于注解方式实现配置二级缓存
public interface IUserDao {
   }