一、基于XML的CRUD操作

项目结构:

注意事项:

  1. 持久层接口和持久层接口的映射文件配置必须在相同的包下
  2. 持久层配置文中mapper标签的namespace属性必须是持久层接口的全限定类名
  3. sql语句的配置标签的id属性必须和持久层方法名相同

1.1 根据ID查询

接口IUserDao.java

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

配置文件IUserDao.xml

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

resultType 属性:用于指定结果集的类型。

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

sql 语句中使用#{}字符: 代表占位符, 相当于原来 jdbc 部分所学的问号?,都是用于执行语句时替换实际的数据。具体的数据是由#{}里面的内容决定的。

测试

/**
 * @author konley
 * @date 2020-07-20 11:16
 * 测试mybatis的crud操作
 */
public class Test {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;
    //执行前运行
    @Before
    public void init() throws IOException {
        //读取配置文件
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //使用构建者创建工厂对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //使用工厂获取sqlSession对象
        sqlSession = factory.openSession();
        //使用sqlseesion代理实现dao接口
        userDao = sqlSession.getMapper(IUserDao.class);
    }
    //执行后回收
    @After
    public void destroy() throws IOException {
        //提交事务
        session.commit();
        sqlSession.close();
        in.close();
    }
    
    //测试根据id查询操作
    @org.junit.Test
    public void testFindById(){
        User user = userDao.findById(43);
        System.out.println(user);
    }

1.2 保存(添加)操作

接口

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

配置文件

     <!-- 保存用户 -->
    <insert id="saveUser" parameterType="domain.User">
        <!-- 配置插入后,可以获取插入数据的id -->
        <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
            select last_insert_id()
        </selectKey>
        insert into user(username,address,sex,birthday)value(#{username},#{address},#{sex},#{birthday})
    </insert>

parameterType 属性:代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称,sql 语句中使用#{}字符

#{}的写法:由于我们保存方法的参数是 一个 User 对象,此处要写 User 对象中的属性名称,它用的是 ognl 表达式

ognl 表达式:#{对象.对象}

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

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

测试

//测试保存操作
    @org.junit.Test
    public void testSave(){
        User user = new User(
                0,"konley","广东","男",new Date(System.currentTimeMillis())
        );
        System.out.println("保存之前的user:"+user);
        userDao.saveUser(user);
        //配置key后user的id会自动更新为插入的id
        System.out.println("保存之后的user"+user);
    }

1.3 更新操作

接口

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

配置文件

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

测试

    //测试更新操作
    @org.junit.Test
    public void testUpdate(){
        User user = new User(
                51,"konleya","武汉","男",new Date(System.currentTimeMillis())
        );
        userDao.updateUser(user);
    }

1.4 删除操作

接口

    /**
     * description: 删除用户
     * @param userId
     * @return void
     */
    void deleteUser(Integer userId);

配置文件

    <!--删除用户-->
    <delete id="deleteUser" parameterType="int">
        delete from user where id=#{id}
    </delete>

测试

    //测试删除操作
    @org.junit.Test
    public void testDelete(){
        userDao.deleteUser(51);
    }

1.5 模糊查询

接口

    /**
     * description: 根据名称模糊查询
     * @param username
     * @return java.util.List<domain.User>
     */
    List<User> findByName(String username);

配置文件

    <!-- 根据名称模糊查询 -->
    <select id="findByName" parameterType="String" resultType="domain.User">
        select * from user where username like #{username}
    <!-- 尽量不使用,推荐使用上面的,防止sql注入
    select * from user where username like '%${value}%' -->
    </select>

测试

    //测试根据名字模糊查询操作
    @org.junit.Test
    public void testFindByName(){
        List<User> users = userDao.findByName("%小%");
        for (User user : users) {
            System.out.println(user);
        }
    }

1.6 聚合函数查询

接口

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

配置文件

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

测试

    //测试查询记录条数操作
    @org.junit.Test
    public void testFindTotal(){
        int total = userDao.findTotal();
        System.out.println(total);
    }

1.7 Mybatis 与 JDBC 编程的比较

  • 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。

    • 解决:在 SqlMapConfig.xml 中配置数据链接池,使用连接池管理数据库链接
  • Sql 语句写在代码中造成代码不易维护,实际应用 sql 变化的可能较大, sql 变动需要改变 java 代码。

    • 解决:将 Sql 语句配置在 XXXXmapper.xml 文件中与 java 代码分离
  • 向 sql 语句传参数麻烦,因为 sql 语句的 where 条件不一定,可能多也可能少,占位符需要和参数对应。

    • 解决:Mybatis 自动将 java 对象映射至 sql 语句,通过 statement 中的 parameterType 定义输入参数的类型。
  • 对结果集解析麻烦, sql 变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成 pojo 对象解析比较方便。

    • 解决:Mybatis 自动将 sql 执行结果映射至 java 对象,通过 statement 中的 resultType 定义输出结果的类型

二、参数

2.1 parameterType 配置参数

SQL 语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)m同时也可以使用实体类的包装,这里介绍如何使用实体类的包装类作为参数传递

2.2 传递实体类包装对象

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

1.编写QueryVo

package domain;

/**
 * @author konley
 * @date 2020-07-21 8:57
 */
public class QueryVo {
    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

2.编写接口

    /**
     * description: 根据queryVo中的条件查询用户
     * @param vo
     * @return java.util.List<domain.User>
     */
    List<User> findUserByVo(QueryVo vo);

3.配置映射

    <!--根据queryVo的条件查询用户-->
    <!-- 根据名称模糊查询 -->
    <select id="findUserByVo" parameterType="domain.QueryVo" resultType="domain.User">
        select * from user where username like #{user.username}
    </select>

4.测试

    //测试使用QueryVo作为查询条件
    @org.junit.Test
    public void testFindByVo(){
        QueryVo vo = new QueryVo();
        User user = new User();
        user.setUsername("%小%");
        vo.setUser(user);

        List<User> users = userDao.findUserByVo(vo);
        for (User u : users) {
            System.out.println(u);
        }
    }

三、结果封装

3.1 resultType类型

resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型

需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名没有注册过的必须使用全限定类名

但实体类属性和数据库列名不一致时,可以在sql语句里使用别名(方法一)

<select id="findAll" resultType="domain.User">
        select id as userID,username as userName,address as userAddress...from user
    </select>

也可以使用下面的resultMap类型(方法二)

3.2 resultMap类型

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

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

映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。

1.定义resultMap

    <!--配置查询结果的列名和实体类的属性名的对应关系-->
    <resultMap id="userMap" type="domain.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>
  • id 标签:用于指定主键字段
  • result 标签:用于指定非主键字段
  • column 属性:用于指定数据库列名
  • property 属性:用于指定实体类属性名称

2.映射配置

    <select id="findAll" resultMap="userMap">
        select * from user
    </select>
  • resultMap属性:使用定义resultmap时的id属性

四、基于传统Dao实现类的CRUD(了解)

4.1 Dao和Mapper接口代理区别

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

而现在主流的开发方式是接口代理开发方式,这种方式总体上更加简便。

但是Dao开发也是被支持了,这里补充Dao实现类开发的CRUD

4.2 持久层Dao接口

/**
 * @author konley
 * @date 2020-07-20 11:14
 * 用户的持久层接口
 */
public interface IUserDao {
    /**
     * description: 查询所有用户
     * @param
     * @return java.util.List<domain.User>
     */
    List<User> findAll();


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

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

    /**
     * description: 删除用户
     * @param userId
     * @return void
     */
    void deleteUser(Integer userId);

    /**
     * description: 根据id查询
     * @param userId
     * @return domain.User
     */
    User findById(Integer userId);

    /**
     * description: 根据名称模糊查询
     * @param username
     * @return java.util.List<domain.User>
     */
    List<User> findByName(String username);

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

}

4.3 持久层Dao接口实现类

/**
 * @author konley
 * @date 2020-07-21 10:02
 * dao实现类方式实现
 */
public class UserDaoImpl implements IUserDao {

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

    @Override
    public List<User> findAll() {
        SqlSession sqlSession = factory.openSession();
        //参数:配置文件中namespace加上方法id
        List<User> users = sqlSession.selectList("dao.IUserDao.findAll");
        sqlSession.close();
        return users;
    }

    @Override
    public void saveUser(User user) {
        SqlSession sqlSession = factory.openSession();
        sqlSession.insert("dao.IUserDao.saveUser",user);
        sqlSession.commit();
        sqlSession.close();
    }

    @Override
    public void updateUser(User user) {
        SqlSession sqlSession = factory.openSession();
        sqlSession.update("dao.IUserDao.updateUser",user);
        sqlSession.commit();
        sqlSession.close();
    }

    @Override
    public void deleteUser(Integer userId) {
        SqlSession sqlSession = factory.openSession();
        sqlSession.delete("dao.IUserDao.deleteUser",userId);
        sqlSession.commit();
        sqlSession.close();
    }

    @Override
    public User findById(Integer userId) {
        SqlSession sqlSession = factory.openSession();
        User user = sqlSession.selectOne("dao.IUserDao.findById", userId);
        sqlSession.close();
        return user;
    }

    @Override
    public List<User> findByName(String username) {
        SqlSession sqlSession = factory.openSession();
        List<User> users = sqlSession.selectList("dao.IUserDao.findByName", username);
        sqlSession.close();
        return users;
    }

    @Override
    public int findTotal() {
        SqlSession sqlSession = factory.openSession();
        int count = sqlSession.selectOne("dao.IUserDao.findTotal");
        sqlSession.close();
        return count;
    }
}

4.4 持久层配置文件

<?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="dao.IUserDao">

    <!-- 查询所有 -->
    <select id="findAll" resultType="domain.User">
        select * from user
    </select>

    <!-- 保存用户 -->
    <insert id="saveUser" parameterType="domain.User">
        <!-- 配置插入后,获取插入数据的id -->
        <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
            select last_insert_id()
        </selectKey>
        insert into user(username,address,sex,birthday)value(#{username},#{address},#{sex},#{birthday})
    </insert>

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

    <!--删除用户-->
    <delete id="deleteUser" parameterType="int">
        delete from user where id=#{id}
    </delete>

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

    <!-- 根据名称模糊查询 -->
    <select id="findByName" parameterType="String" resultType="domain.User">
        select * from user where username like #{username}
    <!--
    尽量不使用,推荐使用上面的,防止sql注入
    select * from user where username like '%${value}%'
    -->
    </select>

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

4.4 测试类

/**
 * @author konley
 * @date 2020-07-20 11:16
 * 测试使用dao的crud操作
 */
public class Test {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;
    //执行前运行
    @Before
    public void init() throws IOException {
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //使用工厂对象 创建dao对象
        userDao = new UserDaoImpl(factory);
    }
    //执行后回收
    @After
    public void destroy() throws IOException {
        in.close();
    }

    //测试查询操作
    @org.junit.Test
    public void findAll(){
        List<User> all = userDao.findAll();
        for (User user : all) {
            System.out.println(user);
        }
    }

    //测试保存操作
    @org.junit.Test
    public void testSave(){
        User user = new User(
                0,"konley","广东","男",new Date(System.currentTimeMillis())
        );
        userDao.saveUser(user);
    }

    //测试更新操作
    @org.junit.Test
    public void testUpdate(){
        User user = new User(
                49,"konleya","武汉","男",new Date(System.currentTimeMillis())
        );
        userDao.updateUser(user);
    }

    //测试删除操作
    @org.junit.Test
    public void testDelete(){
        userDao.deleteUser(49);
    }

    //测试根据id查询操作
    @org.junit.Test
    public void testFindById(){
        User user = userDao.findById(43);
        System.out.println(user);
    }

    //测试根据名字模糊查询操作
    @org.junit.Test
    public void testFindByName(){
        List<User> users = userDao.findByName("%小%");
        for (User user : users) {
            System.out.println(user);
        }
    }

    //测试查询记录条数操作
    @org.junit.Test
    public void testFindTotal(){
        int total = userDao.findTotal();
        System.out.println(total);
    }


}

五、主配置文件补充

5.1 配置文件的内容和顺序

-properties(属性)
    --property

-settings(全局配置参数)
    --setting

-typeAliases(类型别名)
    --typeAliase
    --package

-typeHandlers(类型处理器)

-objectFactory(对象工厂)

-plugins(插件)

-environments(环境集合属性对象)
    --environment(环境子属性对象)
        ---transactionManager(事务管理)
        ---dataSource(数据源)

-mappers(映射器)
    --mapper
    --package

5.2 properties(属性)

第一种配置方式

<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>

第二种配置方式(推荐)

1. 编写数据库配置文件jdbc.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
username=root
password=123456

2. 配置properties标签

    <!--
    配置properties
    可以在标签内部配置连接数据库的信息,也可以引用外部的配置文件
    resource属性 = 按照类路径的写法来写
    -->
    <properties resource="jdbc.properties"/>

3. 使用${ }配置连接池的value

    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

5.3 typeAliases(类型别名)

在SqlMapConfig.xml中配置

    <!--使用typeAliases配置别名,一般只用来配置实体类-->
    <typeAliases>
        <!--type为全限定类名,alias为别名-->
        <!-- <typeAlias type="domain.User" alias="User"></typeAlias> -->
        <!-- 用于指定配置别名的包,指定后该包所有的实体类都自动注册别名,类名就是别名,不区分大小写 -->
        <package name="domain"/>
        <package name="其他包"/>
    </typeAliases>

5.4 mappers(映射器)

1. 使用resourece属性

使用相对于类路径的资源

<mappers>
    <mapper resourece="dao/IUserDao.xml"></mapper>
</mappers>

2. 使用class属性

使用 mapper 接口类路径

<mappers>
    <mapper class="dao.IUserDao"></mapper>
</mappers>

3. 使用package标签

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

<mappers>
    <package name="dao"></package>
</mappers>

注意:方法2和方法3要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中