Mybatis学习笔记

基础知识

对原生JDBC程序问题总结

  • 数据库连接,在使用时立即创建,用完即释放,对数据库进行频繁的连接开启和关闭,造成数据库资源浪费,影响数据库性能。

解决方案:使用数据库连接池管理数据库连接。 

  • SQL语句硬编码到Java程序中,如果SQL语句需要修改,需要重新编译Java代码,不利于系统维护。

解决方案:将SQL语句配置在XML配置文件中,即使SQL变化,不需要对JAVA代码进行重新编译。 

  •  向preparedStatement中设置参数,对占位符号位置和设置参数值硬编码在Java代码中,不利于系统维护。

解决方案:将SQL语句及占位符和参数都配置在XML配置文件中。 

  • 从resultSet中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码,不利于系统维护。

解决方案:将查询的结果集自动映射为Java的POJO对象。 

Mybatis框架原理

Mybatis是什么?

  • Mybatis是一个持久化层的框架,是Apache旗下的顶级项目。
  • Mybatis让程序将主要精力放在SQL上,通过Mybatis提供的映射方式,自由灵活生成(半自动化)满足需要的SQL语句。
  • Mybatis可以将输入参数自动进行输入映射,将结果集自动映射为Java的POJO对象(输出映射)。

Mybatis框架

  • SqlMapConfig.xml(Mybatis的全局配置文件)

配置数据源、事务等Mybatis运行环境 

配置映射文件(在mapper.xml中配置SQL语句)

  • SqlSessionFactory(会话工厂)

作用:创建会话sqlSession 

  • SqlSession(会话)

作用:操作数据库(发出SQL,增、删、改、查)  

  • Executor(执行器)

作用:SqlSession内部通过执行器操作数据库 

  • mapped statement(底层封装对象)

作用: 对操作数据库存储封装,包括SQL语句、输入参数、输出结果类型。

  • MySQL数据库

Mybatis入门程序

SqlMapConfig.xml全局配置文件

<?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">
<configuration>
    <!--  和Spring整合后就无需environments  -->
    <environments>
        < environment>
            <!--  使用JDBC事务管理,事务由Mybatis控制  -->
            <transactionManager type="JDBC" />
            <!--  数据库连接池,由Mybatis管理  -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/..." />
                <property name="username" value="root" />
                <property name="password" value="admin123" />
            </dataSource>
        </environment>
    </environments>
    <!--  加载映射文件  -->
    <mappers>
        <mapper resource="sqlMap/User.xml" />
    </mappers>
</configuration>

根据Id查询用户-映射文件(User.xml)

<?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">
<!--
    namespace命名空间,作用就是对SQL进行分类话管理,理解SQL隔离
    注意:使用mapper代理方法开发,namespace必须等于mapper接口的全路径。
-->
<mapper namespace="test">
    
    <!--  在映射文件中可以配置很多SQL语句  -->
    <!--  需求:通过Id查询用户表的记录  -->
    <!--
          通过select执行数据库查询
          id:标识映射文件中的SQL
          将SQL语句封装到mappedStatement对象中,所以将Id称为statement的Id
          parameterType:指定输入参数的类型。
          #{}:表示一个占位符号
          #{id}:其中Id表示接收的输入参数,参数名称就是Id,
                如果输入参数是简单类型,#{}中的参数名可以任意。
          resultType:指定SQL输出结果的单条记录映射的Java对象。
    -->
    
    <select id="findUserById" parameterType="int" resultType=“xxx.xxx.UserPO”>
        SELECT * 
            FROM USER 
            WHERE id=#{id}
    </select>



    <!--  
        根据用户名称模糊查询用户信息,可能返回多条值。
        resultType:指定的就是单条记录所映射的Java对象类型。
        ${}:表示拼接sql串,将接收到的参数内容不加任何修饰拼接在sql中。
        安全隐患:使用${}会导致SQL注入
        ${value}:接收输入参数的内容,如果传入的参数是简单类型,${}中只能使用value。
    -->
    <select id="findUserByName" parameterType="java.lang.String" resultType="xxx.xxx.UserPO">
        SELECT * 
            FROM USER 
            WHERE username like '%${value}%'
    </select>


    
    <!--   添加用户 -->
    <!--
        parameterType:指定输入参数类型为POJO类。
        #{attribute}:指定POJO类中的属性字段,Mybatis通过OGNL技术获取对象的属性值。
    -->
    <insert id="insertUser" parameterType="xxx.xxx.UserPO">
        
        <!--
            一:自增主键
            将插入数据的主键返回到user对象中
            SELECT LAST_INSERT_ID():得到刚插入数据记录的主键值,只适用于自增主键。
            keyProperty:将查询到的主键值设置到parameterType指定的对象的特定属性中。
            resultType:指定SELECT LAST_INSERT_ID()的结果类型。
            order:SELECT LAST_INSERT_ID()执行顺序,相对于insert语句的顺序。
        -->
        <selectKey keyProperty="id" resultType="java.lang.Integer" order="AFTER">
            SELECT LAST_INSERT_ID()
        </selectKey>

        <!--
            二:非自增主键
            使用MySQL的uuid()生成主键。
            执行过程:
                首先通过uuid()生成主键值,将主键值设置到user对象的id中;
                其次在insert执行时,从user对象中取出id属性值。
        -->
        <selectKey keyProperty="id" resultType="java.lang.String" order="BEFORE">
            SELECT uuid()
        </selectKey>

        INSERT INTO user(id, username, birthday, gender, address) 
            VALUES(#{id}, #{username}, #{birthday}, #{gender}, #{address})
    </insert>



    <!--  删除用户  -->
    <!--
        根据id删除用户,输入参数为id。
    -->
    <delete id="deleteUser" parameterType="java.lang.Integer">
        DELETE FROM USER 
            WHERE id=#{id}
    </delete>

    

    <!--  更新用户  -->
    <!--
        需要输入更新的UserPO对象,必须包括要更新user的id值。
    -->
    <update id="updateUser" parameterType="xxx.xxx.UserPO">
        UPDATE USER 
            SET username=#{username}, birthday=#{birthday}, gender=#{gender}, address=#{address}
            WHERE id=#{id}
    </update>

</mapper>

Mybatis入门程序

public class Mybatis {

    /**
     *根据ID查询用户信息,得到一条记录结果。
     */
    @Test
    public void findUserByIdTest() throws IOException {

        //Mybatis配置文件
        String resource = "SqlMapConfig.xml";

        //得到配置文件流
        InputStream inputStream = new InputStream(resource);

        //创建回话工厂,传入Mybatis的配置文件信息
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //通过工厂得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //通过sqlSession操作数据库
        //第一个参数:映射文件中的statement的id,等于namespace+"."+statement的id。
        //第二个参数:指定和映射文件中所匹配的parameterType类型的参数
        //sqlSession.selectOne的结果是与映射文件中所匹配的resultType类型的对象。
        User user = sqlSession.selectOne("test.findUserById", 1);
         
        //关闭会话
        sqlSession.close();
    }


    /**
     *通过名字模糊查询用户信息列表
     */
    @Test
    public void findUserByNameTest() throws IOException {
        
        //Mybatis配置文件
        String resource = "SqlMapConfig.xml";

        //得到配置文件流
        InputStream inputStream = new InputStream(resource);

        //创建回话工厂,传入Mybatis的配置文件信息
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //通过工厂得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
    
        //users中的user和映射文件中resultType所指定的类型抑制。
        List<User> users = sqlSession.selectList("test.findUserByName", "小明");

        //关闭会话
        sqlSession.close();
    }


    /**
     *添加用户
     */
    @Test
    public void insertUserTest() throws IOException {
        
        //Mybatis配置文件
        String resource = "SqlMapConfig.xml";

        //得到配置文件流
        InputStream inputStream = new InputStream(resource);

        //创建回话工厂,传入Mybatis的配置文件信息
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //通过工厂得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        
        //插入对象
        User user = new User(10001, "Jack", new Date(), 1, "浙江杭州");
        sqlSession.insert("test.insertUser", user);

        //提交事务
        sqlSession.commit();

        //关闭会话
        sqlSession.close();
    }


    /**
     *根据id删除用户
     */
    @Test
    public void deleteUserTest() throws IOException {
        
        //Mybatis配置文件
        String resource = "SqlMapConfig.xml";

        //得到配置文件流
        InputStream inputStream = new InputStream(resource);

        //创建回话工厂,传入Mybatis的配置文件信息
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //通过工厂得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        
        //根据id删除用户
        int id = 10001;
        sqlSession.insert("test.insertUser", id);

        //提交事务
        sqlSession.commit();

        //关闭会话
        sqlSession.close();        
    }

    
    /**
     *更新用户信息
     */
    @Test
    public void updateUserTest() throws IOException {
        
        //Mybatis配置文件
        String resource = "SqlMapConfig.xml";

        //得到配置文件流
        InputStream inputStream = new InputStream(resource);

        //创建回话工厂,传入Mybatis的配置文件信息
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //通过工厂得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        
        //更新用户信息,id必须存在。
        int id = 10001;
        User user = new User(id, "Rose", new Date(), 2, "浙江杭州");
        sqlSession.insert("test.insertUser", user);

        //提交事务
        sqlSession.commit();

        //关闭会话
        sqlSession.close();
    }
}

小结:

  • #{}

#{}表示一个占位符,接收的输入参数类型可以是简单类型、POJO、HashMap等。如果接收简单类型,#{}中可以写成value或其他任意名字。如果接收POJO对象,通过OGNL技术读取对象中的属性值,如:user.username、order.user.username。 

  • ${}

${}表示一个拼接符号,会引入SQL注入,不建议使用。接收的输入参数类型可以是简单类型、POJO、HashMap等。如果接收简单类型,${}中只能写成value。 

Mybatis与Hibernate本质区别和应用场景

Hibernate:是一个标准的ORM(对象关系映射)框架,入门门槛较高,不需要编写SQL语句,Hibernate会会自动生成,因此对SQL语句进行修改和优化比较困难。

应用场景:使用需求变化小的中小型项目,如后台管理系统、ERP、ORM和OA等。

Mybatis:专注于SQL语句本身,需要工程师自己编写SQL语句,对SQL的修改和优化比较方便。Mybatis是一个不完全的ORM框架,虽然需要工程师自己写SQL语句,但Mybatis实现了POJO的映射(输入映射、输出映射)。

应用场景:适用于需求变化较大的项目,如互联网项目。

企业进行技术选型,以低成本、高回报作为原则,根据项目组的技术成熟度进行选择。

Mybatis开发DAO的两种方式:

SqlSession使用范围

SqlSessionFactoryBuilder

通过SqlSessionFactory创建SqlSessionFactory

SqlSessionFactory

通过SqlSessionFactory创建SqlSession,使用单例模式管理sqlSessionFactory。如果Mybatis与Spring整合,则Spring创建的bean默认为单例。

SqlSession

SqlSession是面向用户的接口,但其实现类是线程不安全的,在SqlSession实现类中除了有接口中的方法(操作数据库的方法),还存在数据域属性。所以SqlSession最佳应用场合在方法体内,定义成局部变量使用。

原生DAO开发(程序需要编写DAO接口和DAO实现类)(掌握

实现步骤:

  1. 定义DAO接口,编写DAO实现类。
  2. 在DAO实现类中注入SqlSessionFactory,通过SqlSessionFactory实例在方法体内创建SqlSession。

具体实现:

DAO接口

public interface UserDAO {
    
    /**
     *根据id查询用户信息
     */
    UserPO findUserById(int id) throws Exception;

    /**
     *根据名称模糊查询用户信息
     */
    List<UserPO> findUserByName(String name) throws Exception;

    /**
     *添加用户信息
     */
    UserOP insertUser(UserPO user) throws Exception;

    /**
     *删除用户信息
     */
    void deleteUser(int id) throws Exception;
}

DAO接口实现类

public class UserDAOImpl implement UserDAO {
    
    //通过构造函数注入
    private SqlSessionFactory sqlSessionFactory;
    
    @AutoWired
    public UserDAOImpl(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
    
    /**
     *根据id查询用户信息
     */
    public UserPO findUserById(int id) throws Exception {

        SqlSession sqlSession = sqlSessionFactory.openSession();

        return sqlSession.selectOne("test.findUserById", id);
    }

    /**
     *根据名称模糊查询用户信息
     */
    public List<UserPO> findUserByName(String name) throws Exception {

        SqlSession sqlSession = sqlSessionFactory.openSession();
        
        return sqlSession.selectList("test.findUserByName", name);
    }

    /**
     *添加用户信息
     */
    public UserPO insertUser(UserPO user) throws Exception {

        SqlSession sqlSession = sqlSessionFactory.openSession();

        //主键自动会写
        sqlSession.insert("test.insertUser", user);
        
        sqlSession.commit();

        sqlSession.close();

        return user;
    }

    /**
     *删除用户信息
     */
    public void deleteUser(int id) throws Exception {

        SqlSession sqlSession = sqlSessionFactory.openSession();

        sqlSession.delete("test.deleteUser", id);

        sqlSession.commit();

        sqlSession.close();
    }
}

原始DAO存在的问题

  • DAO接口实现类方法中存在大量的模版方法。
  • 调用sqlSession方法时将statement的id硬编码了。
  • sqlSession方法输入参数使用的是范型,当输入参数有误时,编译阶段不会报错,不利于程序开发。

Mybatis的Mapper接口(相当于DAO接口)代理开发(掌握

实现步骤:

  1. 编写UserMapper.xml映射文件。
  2. 编写UserMapper接口(相当于DAO接口)需要遵循一些开发规范,Mybatis可以自动生成UserMapper接口的实现类代理。

开发规范:

  1. 在UserMapper.xml中,namespace属性值要等于UserMapper接口的全路径。
  2. 在UserMapper.java中,接口的方法名要与UserMapper.xml中statement的id保持一致。
  3. 在UserMapper.java中,接口的方法的输入参数类型要与对应的statement指定的parameterType参数类型一致。
  4. 在UserMapper.java中,接口的方法的返回值类型要与对应的statement指定的resultType结果类型一致

具体实现:

UserMapper.java DAO层接口

public interface UserMapper {
    
    /**
     *根据id查询用户信息
     */
    UserPO findUserById(int id) throws Exception;
    
    /**
     *根据名称模糊查询用户信息
     */
    List<UserPO> findUserByName(String name) throws Exception;
}

UserMapper.xml 映射文件

<?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">
<!--
    namespace命名空间,作用就是对SQL进行分类话管理,理解SQL隔离
    注意:使用mapper代理方法开发,namespace必须等于mapper接口的全路径。
-->
<mapper namespace="test">
        
    <select id="findUserById" parameterType="int" resultType=“xxx.xxx.UserPO”>
        SELECT * 
            FROM USER 
            WHERE id=#{id}
    </select>
    
    <select id="findUserByName" parameterType="java.lang.String" resultType="xxx.xxx.UserPO">
        SELECT * 
            FROM USER 
            WHERE username like '%${value}%'
    </select>

</mapper>

SqlMapConfig.xml 全局配置文件

<?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">
<configuration>
    <!--  和Spring整合后就无需environments  -->
    <environments>
        ......
    </environments>
    <!--  加载映射文件  -->
    <mappers>
        <mapper resource="sqlMap/User.xml" />
        <mapper resource="mapper/UserMapper.xml" />
    </mappers>
</configuration>

使用测试

public class Mybatis {
    
    /**
     *根据id查询用户信息
     */
    @Test
    public void findUserByIdTest() {
        
        //Mybatis配置文件
        String resource = "SqlMapConfig.xml";

        //得到配置文件流
        InputStream inputStream = new InputStream(resource);

        //创建回话工厂,传入Mybatis的配置文件信息
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //通过工厂得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //创建UserMapper实例,Mybatis自动生成UserMapper接口的代理对象。
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        
        int id = 10001;

        UserPO user = userMapper.findUserById(id);
        
        sqlSession.close;
    }

    /**
     *根据名称模糊查询用户信息
     */
    @Test
    public void findUserByNameTest() {
        
        //Mybatis配置文件
        String resource = "SqlMapConfig.xml";

        //得到配置文件流
        InputStream inputStream = new InputStream(resource);

        //创建回话工厂,传入Mybatis的配置文件信息
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //通过工厂得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //创建UserMapper实例,Mybatis自动生成UserMapper接口的代理对象。
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        
        String name = “Jack”;

        List<UserPO> users = userMapper.findUserByName(name);
        
        sqlSession.close;
    }

}

小结

代理对象内部调用selectOne和selectList方法

如果Mapper接口方法返回单个POJO对象,代理对象内部通过selectOne方法查询数据库。

如果Mapper接口方法返回集合对象,代理对象内部通过selectList方法查询数据库。

Mapper接口的方法只能有一个输入参数

可以使用POJO或者包装类型的POJO满足不同业务方法的需求。

注意:DAO层可以使用包装类型的POJO作为输入参数,但service业务层最好不要用包装类型的POJO,不利于业务层的扩展。

Mybatis配置文件SqlMapConfig.xml

Mybatis全局配置文件SqlMapConfig.xml的配置内容如下:

properties(属性)

Mybatis按照如下顺序加载属性

  • 在properties元素体内定义的属性首先被读取

  • 然后会读取properties元素中通过resource或url加载的属性,覆盖以读取的同名属性

  • 最后读取parameterType传递的属性,覆盖以读取的同名属性

因此,通过parameterType传递的属性优先级最高,resource和url加载的属性次之,properties元素体内的属性优先级最低。

建议:将属性定义在properties属性文件中,且属性文件中的属性名要有一定的特殊性,以免被优先级高的同名属性覆盖。如jbdc.properties属性文件,其中属性名文jdbc.username、jdbc.password。

settings(全局配置参数)

Mybatis框架运行时配置的一些参数,如二级缓存、延迟加载等控制参数。

typeAliases(类型别名)

针对parameterType和resultType指定的全路径的类型定义别名,简化书写。

<typeAliases>
    <!--
        单个别名定义
        type:类型全路径
        alisa:别名
    -->
    <typeAlias type="xxx.xxx.UserPO" alias="UserPO" />

    <!--
        批量别名定义
        自定包名,myBatis自动扫描包中的类,自动定义别名,别名就是类名(首字母大写或小写)
    -->
    <package name="xxx.xxx" />
</typeAliases>

typeHandlers(类型处理器)

在Mybatis中,通过typeHandlers完成JDBC与Java之间的类型转换。

objectFactory(对象工厂)

plugins(插件)

environments(环境集合属性对象)

environment(环境子属性对象)

transactionManager(事务管理)

dataSource(数据源)

mappers(映射器)

<mappers>
    <!--  通过resource属性一次加载一个映射文件  -->
    <mapper resource="mapper/UserMapper.xml" />

    <!--
        规约:需要将XxMapper.java接口类名与XxMapper.xml映射文件名称保持一致,且在同一个目录中。(前提:Mapper接口方式)
    -->
    <mapper class="xxx.xxx.UserMapper" />

    <!--
        批量加载mapper
        指定mapper接口的包名,Mybatis自动扫描包下面的所有mapper接口。
        同样需要遵守上述“规约”。
    -->
    <package name="xxx.xxx" />
</mappers>

Mybatis核心:

Mybatis输入映射(掌握

通过parameterType指定输入参数的类型,类型可以是简单类型、POJO、HashMap和POJO的包装类型。

/**
 *包装类型的POJO
 */
public class UserQueryPO {
    
    private UserPO user;

    private OrderPO order;

    //GET、SET方法省略
    
}
<mapper>
    
    <!--  综合查询  -->    

    <select id="findOrder" parameterType="UserQueryPO" resultType="Order">
        SELECT *
            FROM ORDER as o
            WHERE
            o.userId = #{user.id} and o.id = #{order.id}
    </select>
</mapper>

Mybatis输出映射(掌握

使用resultType时,如果SQL语句返回结果与resultType的POJO类型中存在一个以上的属性字段名称相同,就能映射成功,其他没有对应属性的字段为默认值。在返回结果是单个数据是,resultType才能指定为简单类型。

Mybatis中使用resultMap完成高级输出结果映射。

如果查询出来的列名与POJO的属性不一致,又想映射到POJO属性上,通过定义一个resultMap对列名和POJO属性名之间做一个映射关系。

<mapper>
    
    <!--  1.定义resultMap  -->
    <resultMap type="user" id="userResultMap">
        <!--
            id表示结果集中的唯一标识
            column:查询出来的列名
            property:POJO中的属性名
        -->
        <id column="_id" property="id"/>
        <!--  result:对普通列名映射定义  -->
        <result column="name" property="username" />
    </resultMap>

    <select id="findUserByName" parameterType="java.lang.String" resultMap="userResultMap">
        SELECT * 
            FROM USER as u
            WHRER
            u.username = #{name}
    </select>
</mapper>

Mybatis的动态SQL(掌握

Mybatis核心就是对SQL语句进行灵活操作,通过表达式进行判断,对SQL进行灵活拼接,组装。

<select id="findUserByName" parameterType="UserPO" resultType="UserPO">
    SELECT *
        FROM USER as u
        <!--  where可以自动去掉条件中的第一个ADN  -->
        <where>
            <if test="user.username != null">
                AND u.username = #{username}
            </if>    
            <if test="...">
                <if test="...">
                    AND ...
                </if>
            </if>
        </where>    
</select>

SQL片段:将通用的SQL语句定义成SQL片段,其他statement中就可以引用该SQL片段。

<mapper>
    <!--
        定义SQL片段
        id:SQL片段的唯一标识
        经验:SQL片段是基于单表定义的,这样SQL片段可重用性高。
            在SQL片段中不要包含where标签。
    -->
    <sql id="query_user_where">
        <if test="user.username != null">
                AND u.username = #{username}
            </if>    
            <if test="...">
                <if test="...">
                    AND ...
                </if>
            </if>
        </if>
    </sql>

    <select id="findUserByName" parameterType="UserPO" resultType="UserPO">
        SELECT *
            FROM USER as u
        <where>
            <!--  引用SQL片段id,如果refid指定的SQL片段id不在该mapper文件中,需要加上对应mapper文件的namespace(命名空间)的值  -->
            <include refid="query_user_where"/>
        </where>
    </select>
</mapper>

foreach:向SQL传递数组或List,Mybatis使用foreach解析。

<mapper>
    <select id="..." parameterType="UserPO" resultType="UserPO">
        SELECT * FROM USER AS u
        <where>
            <if test="ids != null">
                <!--  AND u.id IN(10,32,76)  -->
                AND u.id IN
                <foreach collection="ids" item="id" open="(", close=")" separator=",">
                    #{id}
                </foreach>
            </if>
        </where>
        
        <where>
            SELECT * FROM USER AS u
            <if test="ids != null">
                <!--  AND (u.id=10 OR u.id=32 OR u.id=76)   -->
                AND
                <foreach collection="ids" item="id" open="(" close=")" separator="OR">
                u.id=#{id}
                </foreach>
            </id>
        </where>
        
    </select>
</mapper>

高级知识

订单商品数据模型分析

分析数据模型的思路

  • 每张表记录的数据内容:分模块对每张表记录的内容进行熟悉,相当于学习系统功能的过程。
  • 每张表重要字段的设置:非空字段、外键字段。
  • 数据库级别表与表之间的关系:外键关系。
  • 表与表之间的业务关系:建立在某个业务基础上分析表与表之间的业务关系。

数据模型的分析

用户表user:购买商品的用户信息。(字段:id、username、)

id:自增主键。

订单表order:用户创建的订单。(字段:id、user_id、number、createtime、note)

id:自增主键。

number:订单号。

user_id:外键。order表为子表,user表为父表。

订单明细表orderdetail:订单详明细。(字段:id、order_id、item_id、item_num)

id:自增主键

order_id:外键。orderdetail表为子表,order表为父表。

item_id:外键。orderdetail表为子表,item表为父表。

商品信息表item:商品详细信息。(字段:id、name、price、detail、pic、createtime)

id:自增主键。

首先分析数据库级别存在外键关系的表与表之间的业务关系:

order外键:user_id

user--->order:一对多,一个用户可以创建多个订单。

order--->user:一对一,一个订单只能由一个用户创建。

orderdetail外键:order_id

order--->orderdetail:一对多,一个订单可以包含多个订单明细,一个订单可以包含多个商品,一个商品信息记录在一个订单明细中。

orderdetail--->order:一对一,一个订单明细只能存在于一个订单中。

orderdetail外键:item_id

orderdetail--->item:一对一,一个订单明细中只包含一个商品信息。

item--->orderdetail:一对多,一个商品可以包含在多个订单明细中。

其次分析数据库级别没有关系的表之间是否有业务关系:

order与item:通过订单明细表作为桥梁,order与item之间是多对多关系。一个订单对应多个订单明细,一个订单明细对应一个商品信息,所以一个订单对应多个商品信息。一个商品信息对应多个订单明细,一个订单明细对应一个订单,所以一个商品信息对应多个订单。

user与item:通过订单和订单明细作为桥梁,user与item之间是多对多关系。一个用户对应多个订单,一个订单对应多个订单明细,一个订单明细对应一个商品信息,所以一个用户对应多个商品信息。一个商品信息对应多个订单明细,一个订单明细对应一个订单,一个订单对应一个用户,所以一个商品信息对应多个用户。

高级结果集映射(一对一、一对多、多对多)

一对一查询:查询订单信息,关联查询创建订单的用户信息。

SQL语句:根据查询需求确定查询的主表为订单表,关联表为用户表。

SELECT o.*,
       u.username,
       user.gender,
       user.address
    FROM ORDER AS o, USER AS u
    WHERE o.user_id = u.id

resultType:

关联查询POJO定义:

将SQL语句查询的结果映射到POJO中,POJO中必须包含所有的查询列。原始Order的POJO不能满足全部字段,需要创建一个扩展自Order的综合POJO类型。

public class Order {

    private Integer id;
    private Integer userId;
    private String number;
    private Date createtime;
    private String note;
    //get、set省略
}
public class OrderCustom extends {
    
    private String username;
    private Integer gender;
    private String address;

    //get、set省略
}

映射文件OrderMapper.xml

<mapper namespace="xxx.xxx.OrderMApper">
    <select id="findOrderWithUser" resultType="xxx.xxx.OrderCustom">
        SELECT o.*,
               u.username,
               user.gender,
               user.address
            FROM ORDER AS o, USER AS u
            WHERE o.user_id = u.id
    </select>
</mapper>

接口OrderMapper.java

public interface OrderMapper {
    
   List<OrderCustom> findOrderWithUser();    
}

resultMap:

关联查询POJO的定义

public class Order {

    private Integer id;
    private Integer userId;
    private String number;
    private Date createtime;
    private String note;
    private User user;
    //get、set省略
}

映射文件OrderMApper.xml

<mapper namespace="xxx.xxx.OrderMApper">
    
    <resultMap type="xxx.xxx.Order" id="OrderWithUserMap">
        <!--  配置映射的订单信息  -->
        <!--  id:指定查询列中的唯一标识,订单信息中的唯一标识
                如果是多列组成的唯一标识,则定义多个id。       
        -->
        <id column="id" property="id" />
        <result column="user_id" property="userId" />
        ......
        <!--  配置映射的关联用户信息
              association:用于映射关联查询单个对象的信息
              property:要将关联查询的单个对象信息映射到POJO对象中的哪个POJO属性
        -->
        <association property="user" type="xxx.xxx.User">
            <!--  id:关联查询用户的唯一标识
                  column:指定唯一标识的列
                  property:映射到User的哪个属性
            -->
            <id column="user_id" property="id" />
            <result column="username" property="username"/>
            ......
        </association>
    </resultMap>

    <select id="findOrderWithUser" resultMap="OrderWithUserMap">
        SELECT o.*,
               u.username,
               user.gender,
               user.address
            FROM ORDER AS o, USER AS u
            WHERE o.user_id = u.id
    </select>
</mapper>

接口OrderMapper.java

public interface OrderMapper {
    
    List<Order> findOrderWithUser();    
}

实现一对一查询小结:

resultType:相对简单,如果POJO中没有包含查询出来的所有列,需要增加列名对应的属性才能完成映射。如果对查询结果没有特殊要求,建议使用resultType。

resultMap:需要单独定义resultMap,实现比较麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射POJO的属性中。resultMap可以实现延迟加载,而resultType无法实现延迟加载。

一对多查询:查询订单及订单明细的信息

SQL语句:根据查询需求确定查询的主表为订单表,关联查询表为订单明细表。

SELECT o.*,
       u.username,
       u.gender,
       u.address,
       ud.id orderdetail_id,
       ud.item_id,
       ud.item_num,
       ud.order_id
    FROM ORDER AS o, USER AS u, ORDERDETAIL AS od
    WHRER o.user_id = u.id AND o.id = od.order_id

关联查询POJO定义

public class Order {

    private Integer id;
    private Integer userId;
    private String number;
    private Date createtime;
    private String note;
    private User user;
    private List<OrderDetail> orderDetails;
    //get、set省略
}
public class OrderDetail {

    private Integer id;
    private Integer itemId;
    private Integer itemNum;
    private Integer orderId;
    //get、set方法省略
}

映射文件OrderMapper.xml

<mapper>
    <!--  订单关联的用户及订单明细resultMap  -->
    <resultMap type="xxx.xxx.Order" id="OrderWithUserAndOrderDetail">
        <!--  订单信息  -->
        <id column="id" property="id"/>
        <result column="user_id" property="userId" />
        <result column="number" property="number" />
        <result column="createtime" property="createtime" />
        <result column="note" property="note" />
        <!--  用户信息  -->
        <association property="user" javaType="xxx.xxx.User">
            <id column="user_id" property="id"/>
            <result column="username" property="username"/>
            <result column="gender" property="gender"/>
            <result column="address" property="address"/>
        </association>
        <!--  订单明细信息  -->
        <!--
              一个订单关联查询出了多条明细,要使用collection进行映射
              collection:将关联查询到的多条记录映射到集合对象中,记录的主键相同视为同一个订单的多个订单明细
              property:需要映射到Order中的orderDetails的属性上
              ofType:指定映射到List集合属性中的POJO类型
        -->
        <collection property="orderDetails" ofType="xxx.xxx.OrderDetail">
            <!--  id:订单明细的唯一标识  -->
            <id column="orderdetail_id" property="id" />
            <result column="item_id" property="itemId" />
            <result column="item_num" property="itemNum" />
            <result column="order_id" property="orderId" />
        <collection>
    </resultMap>
    
    <!--  查询订单及关联的用户和订单明细信息  -->
    <select id="findOrderWithUserAndOrderDetail" resultMap="OrderWithUserAndOrderDetail">
        SELECT o.*,
               u.username,
               u.gender,
               u.address,
               ud.id orderdetail_id,
               ud.item_id,
               ud.item_num,
               ud.order_id
        FROM ORDER AS o, USER AS u, ORDERDETAIL AS od
        WHRER o.user_id = u.id AND o.id = od.order_id
    </select>
</mapper>

接口OrderMapper.java

public interface OrderMapper {

    List<Order> findOrderWithUserAndOrderDetail();
}

多对多查询:查询用户及用户购买的商品信息

SQL语句:根据查询需求确定查询的主表为用户表,由于用户和商品没有直接关联,通过订单和订单明细进行关联,所以关联表为:订单表、订单明细表、商品表。

SELECT o.*,
       u.username,
       u.gender,
       u.address,
       ud.id orderdetail_id,
       ud.item_id,
       ud.item_num,
       ud.order_id
       i.name item_name,
       i.detail item_detail,
       i.price item_price,
       i.prc item_pic
    FROM ORDER AS o, USER AS u, ORDERDETAIL AS od, ITEM AD i
    WHRER o.user_id = u.id AND o.id = od.order_id AND od.item_id = i.id

关联查询POJO定义

public class User {
    private Integer id;
    private String username;
    private Integer gender;
    private String address;
    private List<Order> orders;
    //set、get方法省略
}

public class Order {
    private Integer id;
    private Integer userId;
    private String number;
    private List<OrderDetail> orderDetails;
    //set、get方法省略
}

public class OrderDetail {
    private Integer id;
    private Integer orderId;
    private Integer itemId;
    private Integer itemNum;
    private Item item;
    //set、get方法省略
}

public class Item {
    private Integer id;
    private String name;
    private String detail;
    private Double price;
    private String picture;
    //set、get方法省略
}

映射文件UserMapper.xml

<mapper namespace="xxx.xxx.UserMapper">
    
    <!--  查询用户及购买的商品  -->
    <resultMap type="xxx.xxx.User" id="UserWithItem">
        <id column="user_id" property="id" />
        <result column="username" property="username" />
        <result column="gender" property="gender" />
        <result column="address" property="address" />
        <collection property="orders" ofType="xxx.xxx.Order">
            <id column="id" property="id" />
            <result column="number" property="number" />
            <result column="createtime" property="create time" />
            <result column="note" property="note" />
            <collection property="orderDetails" ofType="xxx.xxx.OrderDetail">
                <id column="orderdetail_id" property="id" />
                <result column="order_id" property="orderId" />
                <result column="item_id" property="itemId" />
                <result column="item_num" property="itemNum" />
                <association property="item" javaType="xxx.xxx.Item">
                    <id column="item_id" property="id" />
                    <result column="item_name" property="name" />
                    <result column="item_detail" property="detail" />
                    <result column="item_price" property="price" />
                    <result column="item_pic" property="picture" />
                </association>
            </collection>
        </collection>
    </resultMap>
    
    <select id="findUserWithItems" resultMap="UserWithItem">
        SELECT o.*,
               u.username,
               u.gender,
               u.address,
               ud.id orderdetail_id,
               ud.item_id,
               ud.item_num,
               ud.order_id
               i.name item_name,
               i.detail item_detail,
               i.price item_price,
               i.prc item_pic
            FROM ORDER AS o, USER AS u, ORDERDETAIL AS od, ITEM AD i
            WHRER o.user_id = u.id AND o.id = od.order_id AND od.item_id = i.id
    </select>
</mapper>

接口UserMapper.java

public interface UserMapper {
    List<User> findUserWithItems();
}

resultMap总结

resultType:

作用:将查询结果按照SQL语句返回结果的列名与POJO属性名一致的映射到POJO中。

场合:常见一些明细记录的展示,比如:用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每条记录映射到POJO中,在前端页面遍历list即可。

resultMap:

使用association和collection分别完成一对一和一对多的高级映射。

association:

作用:将关联查询信息映射到一个POJO中。

场合:为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的POJO属性中,比如:查询订单及关联的用户信息。使用resultType无法将查询结果映射到POJO对象的POJO属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。

collection:

作用:将关联查询信息映射到一个List集合中。

场合:为了方便查询遍历关联信息,可以使用collection将关联信息映射到List集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块List中,将菜单列表映射到模块对象的菜单List属性中,方便对查询结果集进行遍历查询。如果使用resultType无法将查询结果集映射到List集合中。

Mybatis延迟加载

什么是延迟加载?

resultMap可以实现高级映射(使用association和collection分别实现一对一及一对多映射),association、collection具备延迟加载功能。

需求:如果查询订单且关联查询用户信息,如果先查订单信息即可满足要求,当需要查询用户信息时再查询用户信息。把对用户信息的按需查询称为延迟加载。

延迟加载:先从单表中查询,需要时再从关联表进行关联查询,大幅提高数据库性能,因为查询单表要比查询多张表数度快。

使用association实现延迟加载

 

Mybatis查询缓存(一级缓存、二级缓存)

Mybatis和Spring进行整合(掌握

 

Mybatis逆向工程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值