Mybatis笔记

Mybatis

注:本篇博客内容关于Mybatis,后面会讲到原始开发模式和mapper代理开发模式、核心配置文件的配置、输入映射和输出映射、动态sql、高级映射、延迟加载、查询缓存、Mybatis和Spring的整合方式、以及逆向工程。这是我以前自学Java时写过的笔记内容,也有很多类似的其他的相关笔记内容,如:版本控制工具、主流框架、开发工具等。可以到我的远程库下载,链接:https://github.com/15308232110/My-note(这是我第一次写博客,如果喜欢请留言或私信,我会根据情况决定是否继续写博客。顺便提一点,我是一个乐于助人的人哟!)

1. 在原生态JDBC程序中存在的问题

public class Demo {

    @Test
    public void fun(){

        //数据库连接
        Connection connection = null;
        //预编译Statement,预编译可以提高数据库性能
        PreparedStatement preparedStatement = null;
        //结果集
        ResultSet resultSet = null;
        try {
            //加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            //通过驱动管理类获取数据库链接,配置了编码参数
            connection =  DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "123");
            //使用占位符定义sql模板
            String sql = "select * from user where username = ?";
            //获取预处理statement
            preparedStatement = connection.prepareStatement(sql);
            //设置参数
            preparedStatement.setString(1, "王五");
            //向数据库发出sql执行查询,查询出结果集
            resultSet =  preparedStatement.executeQuery();
            //遍历查询结果集
            while(resultSet.next()){
                System.out.println(resultSet.getString("id")+"  "+resultSet.getString("username"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            //释放资源
            try {
                if(resultSet != null)resultSet.close();
                if(preparedStatement != null) preparedStatement.close();
                if(connection != null) connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

1.频繁创建关闭连接,资源浪费!

2.sql语句硬编码在java代码中,不利于项目维护。

3.占位符位置和参数值硬编码在java代码中,不利于项目维护 。

4.遍历结果集数据时,存在硬编码,不利于项目维护。

2. Mybatis概述

1.mybatis是一个持久层框架,最初是apache的顶级项目。它先被托管到了goolecode,后来又被托管到了github

2.mybatis使程序员将主要精力放在sql语句上,通过mybatis提供的映射方式可以灵活地生成满足需要的半自动化sql语句

3.mybatis可以将输入的参数自动进行输入映射,并将查询结果集灵活地映射成java对象,也就是输出映射

4.下载地址:https://github.com/mybatis/mybatis-3/releases

5.hibernate是一个标准ORM框架,由于sql语句是自动生成的,所以对sql语句进行优化或修改比较困难,适用于需求变化不多的中小型项目
而 mybatis是一个不完全的ORM框架,由程序员编写sql语句,并且也可以实现映射(输出映射,输入映射),对sql语句进行优化或修改比较方便,适用于需求变化较多的项目

6.mybatis的执行流程:
mybatis的执行流程图

3. Mybatis入门程序

第一步:导入 mybatis核心jar包及其依赖jar包、数据库驱动jar包、添加log4j日志文件
项目结构
log4j.properties

log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 和spring整合后 environments配置将被废除-->
    <environments default="development">
        <environment id="development">
        <!--由mybatis管理的jdbc事务管理-->
            <transactionManager type="JDBC" />
        <!-- 由mybatis管理的数据库连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
                <property name="username" value="root" />
                <property name="password" value="123" />
            </dataSource>
        </environment>
    </environments>
</configuration>

第二步:创建pojo类

public class User implements Serializable {

    private int id;
    private String username;
    private String sex;
    private Date birthday;
    private String address;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }


    @Override
    public String toString() {
        return "User [id=" + id + ", username=" + username + ", sex=" + sex + ", birthday=" + birthday + ", address=" + address + "]";
    }
}

第三步:配置pojo类对应的映射文件User.xml(在mapper代理开发时,需要命名为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 namespace="test">

    <!--id:映射文件中可以配置很多sql语句,所以id就是sql语句的标识,sql语句的数据信息会被封装到mappedStatement对象中,所以将id也称为statementId-->
    <!--parameterType:输入参数的类型-->
    <!--resultType:输出结果的类型,单条!-->
    <!--#{}:占位符,用来接收输入参数,对于输入参数类型是简单类型,占位符中的参数可以任意的-->
    <select id="findUserById" parameterType="int" resultType="cn.itcast.pojo.User">
        SELECT * FROM user Where id = #{id}
    </select>

    <!--${}:拼接字符串,可能遭遇sql攻击,不建议使用,对于输入参数类型是简单类型,其传入的参数只能是value-->  
    <select id="findUserByUsername" parameterType="java.lang.String" resultType="cn.itcast.pojo.User">
       SELECT * FROM user Where username Like '%${value}%' 
    </select>

    <insert id="insertUser" parameterType="cn.itcast.pojo.User">

        <!--自增主键返回,返回到插入的对象中,不设置默认返回默认值给对象--> 
        <!--SELECT LAST_INSERT_ID():刚插入数据的自增主键值--> 
        <!--keyProperty:指定将主键值设置给parameterType类型对象的哪个属性--> 
        <!--order:相对sql语句返回主键的执行顺序,自增是先插入数据后才有主键值--> 
        <!--resultType:指定主键的输出结果类型--> 
        <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
            SELECT LAST_INSERT_ID()
        </selectKey>

        <!--#{}中指定pojo的属性名,用来接收pojo对象的属性值,mybatis会通过OGNL获取对象的属性值-->
        INSERT INTO user(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})

        <!--非自增主键返回,例如SELECT uuid(),需要在sql语句中添加"id和#{id}",并且相对sql语句返回主键的执行顺序为BEFORE,因为数据在插入之前就应该存在uuid--> 
    </insert>

    <!--可以不需要输出结果类型-->
    <delete id="deleteUser" parameterType="java.lang.Integer">
        DELETE FROM user WHERE id=#{id}
    </delete>

    <update id="updateUser" parameterType="cn.itcast.pojo.User">
        UPDATE user SET username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} WHERE id=#{id}
    </update>

</mapper>

第四步:在SqlMapConfig.xml中加载映射文件

 </environments>
    <mappers>
        <mapper resource="sqlmap/User.xml"/>
    </mappers>
</configuration>

第五步:测试代码

public class Demo {

    @Test
    public void fun() {

        SqlSession sqlSession = null;
        try {
            //根据配置文件创建的工厂
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml"));
            //通过工厂创建会话
            sqlSession = sqlSessionFactory.openSession();

            //1.查询,第一个参数值等于"namespace + '.' + statementId",第二个参数指定sql语句中parameterTyp的参数值,使用selectOne()方法查询出多条语句时会报错
            User user = sqlSession.selectOne("test.findUserById", 10);
            List<User> list = sqlSession.selectList("test.findUserByUsername", "小明");
            System.out.println(user);
            System.out.println(list);

            //2.添加,可以不为对象设置数据库表中的自增主键
            user = new User();
            user.setUsername("李小明");
            user.setBirthday(new Date());
            user.setSex("1");
            user.setAddress("四川成都");
            sqlSession.insert("test.insertUser", user);

            //删除
            sqlSession.delete("deleteUser",28);

            //修改
            user = new User();
            user.setId(10);
            user.setUsername("李大明");
            user.setBirthday(new Date());
            user.setSex("2");
            user.setAddress("成都温江");
            sqlSession.update("updateUser", user);

            //添加、删除、修改操作需要提交事务
            sqlSession.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null)sqlSession.close();
        }
    }
}

4. Mybatis开发Dao的两种方法

1.原始(Ibatis)

SqlSessionFactory一旦创建,就一直使用,所以在mybatis和spring整合时,可以使用单例模式管理。SqlSessionFactory,进而,可以把SqlSessionFactoryBuilder当做一个工具类,在需要创建SqlSessionFactory时候,只需要new一次SqlSessionFactoryBuilder即可。

SqlSession是一个提供了很多操作数据库的方法的接口,因为在其实现类中还有数据域属性,导致其线程不安全,所以其应该使用在方法体中

public interface UserDao {

    public User findUserById(int id) throws Exception;

    public List<User> findUserByUsername(String name) throws Exception;

    public void insertUser(User user) throws Exception;

    public void deleteUser(int id) throws Exception;

    public void updateUser(User user) throws Exception;
}
public class UserDaoImpl implements UserDao {

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

    @Override
    public User findUserById(int id) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = sqlSession.selectOne("test.findUserById", id);
        sqlSession.close();
        return user;

    }

    @Override
    public List<User> findUserByUsername(String name) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<User> list = sqlSession.selectList("test.findUserByUsername", name);
        sqlSession.close();
        return list;
    }

    @Override
    public void insertUser(User user) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.insert("test.insertUser", user);
        sqlSession.commit();
        sqlSession.close();

    }

    @Override
    public void deleteUser(int id) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.delete("test.deleteUser", id);
        sqlSession.commit();
        sqlSession.close();
    }

    @Override
    public void updateUser(User user) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.update("test.updateUser", user);
        sqlSession.commit();
        sqlSession.close();
    }
}
public class Demo {

    private UserDao userDao;

    @Before
    public void setSqlSessionFactory() throws IOException{
        userDao = new UserDaoImpl(new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml")));
    }

    @Test
    public void testFindUserById() throws Exception {
        System.out.println(userDao.findUserById(1));
    }

    @Test
    public void testfindUserByUsername() throws Exception {
        System.out.println(userDao.findUserByUsername("小明"));
    }

    @Test
    public void testInsertUser() throws Exception {
        User user = new User();
        user.setUsername("赵六");
        user.setBirthday(new Date());
        user.setSex("2");
        user.setAddress("成都温江");
        userDao.insertUser(user);
    }

    @Test
    public void testDeleteUser() throws Exception {
        userDao.deleteUser(32);
    }

    @Test
    public void testUpdateUser() throws Exception {
        User user = new User();
        user.setId(1);
        user.setUsername("阿龙");
        user.setBirthday(new Date());
        user.setSex("1");
        user.setAddress("成都简阳");
        userDao.updateUser(user);
    }
}

原始Dao开发方法需要写Dao接口和Dao实现类,并在Dao实现类中注入SqlSessionFactory

存在的问题:
1.Dao接口实现类方法中存在大量重复代码
2.调用SqlSession方法时将statementId 硬编码了。
3.由于SqlSession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错,不利于程序员开发。

2.Mapper代理

Mapper代理Dao开发方法只需要写Mapper接口,它相当于Dao接口。

开发规范
1.映射文件命名为xxxMapper.xml,并且其中namespace属性值为接口类路径。
2.Mapper接口中方法的名称必须与statementId一致。
3.Mapper接口中方法的参数类型必须和parameterType一致。
4.Mapper接口中方法的返回值类型必须和resultType一致。

public interface UserMapper {

    //如果方法返回单个对象,代理对象自动调用selectOne()方法
    public User findUserById(int id) throws Exception;

    //如果方法返回多个对象,代理对象自动调用selectList()方法
    public List<User> findUserByUsername(String name) throws Exception;

    public void insertUser(User user) throws Exception;

    public void deleteUser(int id) throws Exception;

    public void updateUser(User user) throws Exception;
}
public class Demo {

    private SqlSession sqlSession;
    private UserMapper userMapper;

    @Before
    public void setSqlSessionFactory() throws IOException{
        sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml")).openSession();
        userMapper = sqlSession.getMapper(UserMapper.class);
    }

    @Test
    public void testFindUserById() throws Exception {
        System.out.println(userMapper.findUserById(1));
        sqlSession.close();
    }

    @Test
    public void testfindUserByUsername() throws Exception {
        System.out.println(userMapper.findUserByUsername("小明"));
        sqlSession.close();
    }

    @Test
    public void testInsertUser() throws Exception {
        User user = new User();
        user.setUsername("赵雷");
        user.setBirthday(new Date());
        user.setSex("2");
        user.setAddress("成都温江");
        userMapper.insertUser(user);
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void testDeleteUser() throws Exception {
        userMapper.deleteUser(38);
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void testUpdateUser() throws Exception {
        User user = new User();
        user.setId(1);
        user.setUsername("阿飞");
        user.setBirthday(new Date());
        user.setSex("1");
        user.setAddress("成都简阳");
        userMapper.updateUser(user);
        sqlSession.commit();
        sqlSession.close();
    }
}       

5. SqlMapConfig.xml文件

1. Properties(属性)

可以用来引入配置文件,方便对信息统一管理,还可以定义属性。

db.properties文件

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

使用resource属性或url属性引入db.properties文件、定义属性,并加载db.properties文件

<configuration>
    <properties resource="db.properties">
        <property name="xxx" value="XXX"></property>
    </properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <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按照顺序加载属性,同名属性的属性值会被覆盖
在properties元素体内定义的属性首先被读取。
由properties元素引入文件中的属性之后被读取。
由parameterType传递的属性最后被读取。

2. Settings(全局参数)

可以配置mybatis全局参数来影响mybatis的运行行为
全局参数
全局参数
全局参数
3. typeAliases(别名)

mybatis提供了别名,也可以自定义别名
mybatis默认提供的别名

<typeAliases>
        <!--单个别名定义,type指类路径,alias指别名-->
        <typeAlias alias="user" type="cn.itcast.mybatis.po.User"/>
        <!--批量别名定义,包中所有类的别名都为类名的首字母大写或小写,这种方式不允许指定范围内存在同名类,扩大范围只能写多个package或减少包的层级,不能使用通配符-->
        <package name="cn.itcast.pojo"/>
        <package name="cn.itcast.dao"/>
    </typeAliases>

4. mappers(映射器)

<mappers>
        <!--加载相对类路径下的映射文件-->
        <mapper resource="cn/itcast/mapper/UserMapper.xml" />
        <!--加载完全限定路径的映射文件-->
        <mapper url="file:///F:/development/workspaces/2018.7.13/cn/itcast/mapper/UserMapper.xml" />
        <!--通过Mapper接口加载映射文件,要求映射文件和Mapper接口在同一个包下,且名称相同-->
        <mapper class="cn.itcast.mapper.UserMapper"/>
        <!--扫描包中所有Mapper接口并加载对应的映射文件,同样要求映射文件和Mapper接口在同一个包下,且名称相同-->
        <package name="cn.itcast.mapper"/>
    </mappers>

6. 输入映射

pojo包装类parameterType只能有一个,当一个pojo简单类型包含的属性不足以满足输入的条件时,可以写一个类来包含所有需要的输入参数,这就是pojo包装类。

需要先定义一个pojo扩展类,其通过继承得到pojo简单类属性,再封装扩展数据。
pojo包装类则只需通过封装多个扩展类即可,再将其设置为parameterType,就可以通过OGNL找到需要的输入参数(这块儿内容在之后的代码中可以看到实际使用的场景)。

当parameterType使用hashmap类型时,需要给hashmap对象添加查询数据的键值对,key为查询的列名称,value为指定查询的数据。

7. 输出映射

1. resultType

对于pojo类型,查询出来的列名必须和pojo中的属性名一致,才能映射成功,并且只会映射需要查询的内容。
只要查询出来的列名和pojo的属性有一个一致,就会创建pojo对象。
只有当查询出来的列名和pojo的属性名全部不一致,才不会创建pojo对象,但是注意!对于查询多条记录,因为查询到了数据,返回的集合的长度仍然时存在的,因为这是单纯针对数据库,并没有与pojo类相关联。

对于hashmap类型,输出的结果是hashmap对象,其key为查询的列名称,value为查询出的数据。

2.resultMap

对于pojo类型,如果查询出来的列名与pojo中的属性名不一致,还可以使用resultMap来完成映射。

<mapper namespace="cn.itcast.mapper.UserMapper">
    <!--需要先定义resultMap,给定pojo类路径和其唯一标识-->
    <resultMap type="user" id="userResultMap">
        <!--id用于指定查询列中的唯一标识,如果多个列共同组成一个唯一标识,那么需要配多个id-->
        <id column="_id" property="id"/>
        <!--对应普通列-->
        <result column="_username" property="username"/>
        <result column="_birthday" property="birthday"/>
    </resultMap>

    <!--使用resultMap,对于本命空间中定义的resultMap,"namespace."可以省略不些-->
    <select id="findUserByUsername" parameterType="string" resultMap="userResultMap">
       SELECT id _id,username _username,birthday _birthday FROM user Where username Like '%${value}%' 
    </select>
</mapper>

8. 动态Sql

就是根据parameterType对象的属性是否存在,动态添加条件语句

<?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="cn.itcast.mapper.UserMapper">
    <select id="findUser" parameterType="user" resultType="user">
    SELECT * FROM user WHERE username = #{username} AND sex = #{sex}
    <where>
       <if test="username != null">
            AND username = #{username}
        </if>
        <if test="sex != null">
            AND sex = #{sex}
        </if>
       </where>
    </select>
</mapper>

对于大量重复使用的sql片段,可以将其抽取出来,在需要使用时引用即可

<mapper namespace="cn.itcast.mapper.UserMapper">
    <!--定义sql片段,需要给定唯一标识-->
    <sql id="query_user_where">
        <if test="username != null">
        AND username = #{username}
       </if>
        <if test="sex != null">
            AND sex = #{sex}
        </if>
    </sql>
    <select id="findUser" parameterType="user" resultType="user">
        SELECT * FROM user
        <where>
            <include refid="query_user_where"/>
        </where>
    </select>
</mapper>

对于IN和OR这种sql片段,可以使用foreach对其遍历

<mapper namespace="cn.itcast.mapper.UserMapper">
    <sql id="query_user_or">
        <!--这里的ids是pojo中的一个集合成员变量-->
        <if test="ids!=null">
            <!--collection指需要遍历的集合,item指临时变量,open和close指遍历开始前和结束后需要添加的sql片段,separator指遍历间添加的sql片段-->
            <foreach collection="ids" item="id" open="AND id IN(" close=")" separator="," >
                #{id}
            </foreach>
        </if>
    </sql>
    <select id="findUser" parameterType="user" resultType="user">
        SELECT * FROM user
        <where>
            <include refid="query_user_or"/>
        </where>
    </select>
</mapper>
<mapper namespace="cn.itcast.mapper.UserMapper">
    <sql id="query_user_or">
        <if test="ids!=null">
            <foreach collection="ids" item="id" open="id=" separator="OR" >
                #{id}
            </foreach>
        </if>
    </sql>
    <select id="findUser" parameterType="user" resultType="user">
        SELECT * FROM user
        <where>
            <include refid="query_user_or"/>
        </where>
    </select>
</mapper>

9. 高级映射

1.一对一查询

1.1使用resultType

1.1.1 创建pojo扩展类

public class OrdersCustom extends Orders{

    private String username;
    private String sex;
    private String address;

    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    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;
    }
}

1.1.2. 配置sql语句

<mapper namespace="cn.itcast.mapper.OrdersMapperCustom">
    <select id="selectOrdersCustoms" resultType="ordersCustom">
        SELECT o.*,u.username,u.sex,u.address FROM orders o,USER u WHERE o.user_id=u.id
    </select>
</mapper>

1.1.3.创建Mapper接口

public interface OrdersMapperCustom {

    public List<OrdersCustom> selectOrdersCustoms() throws Exception;
}

1.1.4.测试代码

public class Demo {
    @Test
    public void testSelectOrdersCustoms() throws Exception {
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml")).openSession();  
        System.out.println(sqlSession.getMapper(OrdersMapperCustom.class).selectOrdersCustoms());
        sqlSession.close();
    }
}

1.2.使用resultMap

1.2.1. 在Orders类中封装User属性,将关联查询出来的用户信息直接封装到orders对象的user属性中

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

1.2.2.配置sql语句

<mapper namespace="cn.itcast.mapper.OrdersMapper">
    <resultMap type="orders" id="ordersUserResultMap">
        <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可以将关联查询信息映射到一个pojo对象中,javaType指定pojo类路径-->
        <association property="user" javaType="user">
            <!--id用于指定关联查询对象的唯一标识 -->
            <id column="user_id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
           <result column="address" property="address"/>
        </association>
    </resultMap>
    <select id="selectOrders" resultMap="ordersUserResultMap">
        SELECT o.*,u.username,u.sex,u.address FROM orders o,USER u WHERE o.user_id=u.id
    </select>
</mapper>

1.2.3.创建Mapper接口

public interface OrderssMapper {

    public List<Orders> selectOrders() throws Exception;
}

1.2.4.测试代码

public class Demo {
    @Test
    public void testSelectOrdersCustoms() throws Exception {
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml")).openSession();  
        System.out.println(sqlSession.getMapper(OrdersMapper.class).selectOrders());
        sqlSession.close();
    }
}

2.一对多查询

2.1.使用resultType

使用resultType实现一对多与实现一对一无任何区别

2.2.使用resultMap

2.2.1.在Orders类中封装Orderdetails属性,将关联查询出来的所有订单明细直接封装到orders对象的List属性中

private List<Orderdetail> orderdetails;
public List<Orderdetail> getOrderdetails() {
    return orderdetails;
}
public void setOrderdetails(List<Orderdetail> orderdetails) {
    this.orderdetails = orderdetails;
}

2.2.2.配置sql语句

<mapper namespace="cn.itcast.mapper.OrdersMapper">
    <resultMap type="orders" id="ordersUserResultMap">
        <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="user">
            <id column="user_id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
            <result column="address" property="address"/>
        </association>
    </resultMap>
    <!--这里使用了extends,相当于将ordersUserResultMap中的内容写到了这个resultMap中的最前面-->
    <resultMap type="orders" id="ordersUserOrderdetailResultMap" extends="ordersUserResultMap">
        <!--collection可以将关联查询信息映射到一个集合中,ofType指定集合中的pojo类路径-->
        <collection property="orderdetails" ofType="orderdetail">
            <id column="orderdetail_id" property="id"/>
            <result column="items_num" property="itemsNum"/>
        </collection>
    </resultMap>
    <select id="selectOrders" resultMap="ordersUserOrderdetailResultMap">
        SELECT o.*,u.username,u.sex,u.address,s.id orderdetail_id,s.items_num FROM orders o,USER u,orderdetail s WHERE o.user_id=u.id AND o.id=s.orders_id
    </select>
</mapper>

2.2.3.创建Mapper接口

public interface OrdersMapper {

    public List<Orders> selectOrders() throws Exception;
}

2.2.4.测试代码

public class Demo {
    @Test
    public void testSelectOrdersCustoms() throws Exception {
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml")).openSession();  
        System.out.println(sqlSession.getMapper(OrdersMapper.class).selectOrders());
        sqlSession.close();
    }
}

3.一对多查询

3.1.使用resultType

使用resultType实现多对多与仍然与实现一对一无任何区别。

3.2.使用resultMap

3.2.1.
在User类中封装ordersList属性,将关联查询出来的所有订单直接封装到user对象的List属性中
在Orders类中封装Orderdetails属性,将关联查询出来的所有订单明细直接封装到orders对象的List属性中。
在Orderdetail类中封装items属性,将关联查询出来的商品直接封装到orderdetail对象的items属性中

private List<Orders> ordersList; 
    public List<Orders> getOrdersList() {
        return ordersList;
    }
    public void setOrdersList(List<Orders> ordersList) {
        this.ordersList = ordersList;
    }


 private List<Orderdetail> orderdetails;
 public List<Orderdetail> getOrderdetails() {
        return orderdetails;
    }
    public void setOrderdetails(List<Orderdetail> orderdetails) {
        this.orderdetails = orderdetails;
    }
 private Items items; 
 public Items getItems() {
        return items;
    }
    public void setItems(Items items) {
        this.items = items;
    }

3.2.2.配置sql语句

<mapper namespace="cn.itcast.mapper.UserMapper">
    <resultMap type="orders" id="ordersUserResultMap">
        <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="user">
            <id column="user_id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
            <result column="address" property="address"/>
        </association>
    </resultMap>
    <resultMap type="user" id="userOrdersOrderdetailItemsResultMap">
        <id column="user_id" property="id"/>
        <result column="username" property="username"/>
        <collection property="ordersList" ofType="orders">
            <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"/>
            <collection property="orderdetails" ofType="orderdetail">
                <id column="orderdetail_id" property="id"/>
                <result column="items_num" property="itemsNum"/>
                <association property="items" javaType="items">
                    <id column="items_id" property="id"/>
                    <result column="price" property="pic"/>
                </association>
            </collection>
        </collection>
    </resultMap>
    <select id="selectUsers" resultMap="userOrdersOrderdetailItemsResultMap">
        SELECT o.*,u.username,s.id orderdetail_id,s.items_id,s.items_num,i.price FROM orders o,USER u,orderdetail s,items i WHERE o.user_id=u.id AND o.id=s.orders_id AND i.id=s.items_id
    </select>
</mapper>

3.2.3.创建Mapper接口

public interface UserMapper {

    public List<User> selectUsers() throws Exception;
}

3.2.4.测试代码

public class Demo {
    @Test
    public void testSelectOrdersCustoms() throws Exception {
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml")).openSession();  
        System.out.println(sqlSession.getMapper(OrdersMapper.class).selectUsers());
        sqlSession.close();
    }
}

4.小结

resultType实现关联查询较为简单,如果pojo中没有属性对应需要查询出来的列,只需定义pojo扩展类或包装类,即可完成映射。
resultMap则较为复杂,但可以将关联查询信息映射到属性中

10. 延迟加载

1.延迟加载:先查询简单信息、在需要时进一步关联查询所需要的信息,能够大大提高数据库性能,这就是延迟加载

使用resultMap的association和collection可以实现一对一和一对多的延迟加载,也可以在service层中根据需求来判断以实现延迟加载

2.使用resultMap实现延迟加载

2.1.配置延迟加载

<configuration>
    <properties resource="db.properties"/>
    <settings>
        <!--打开延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--将积极加载更改为消极加载,即按需加载-->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    <typeAliases>

2.2.在Orders类中封装User属性,将关联查询出来的用户信息直接封装到orders对象的user属性中

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

2.3.配置sql语句

<mapper namespace="cn.itcast.mapper.UserMapper">
    <select id="findUserById" parameterType="_int" resultType="user">
        SELECT * FROM USER WHERE ID=#{id}
    </select>
</mapper>
<mapper namespace="cn.itcast.mapper.OrdersMapper">
    <resultMap type="orders" id="OrdersLazyLoadingUserResultMap">
        <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"/>
        <!--select用来指定延迟加载是需要执行的sql语句的statementId,column指定关联查询时需要的条件列-->
        <association property="user" javaType="user" select="cn.itcast.mapper.UserMapper.findUserById" column="user_id"/>
    </resultMap>
    <select id="selectOrdersLazyLoadingUser" resultMap="OrdersLazyLoadingUserResultMap">
        SELECT * FROM Orders
    </select>
</mapper>

2.4. 创建Mapper接口

public interface UserMapper {

    public User findUserById(int id) throws Exception;
}

2.5.测试代码

public class Demo {
    @Test
    public void testSelectOrdersCustoms() throws Exception {
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml")).openSession();
        for(Orders orders: sqlSession.getMapper(OrdersMapper.class).selectOrdersLazyLoadingUser()){
            System.out.println(orders.getUser());
        }
        sqlSession.close();
    }
}

11. 查询缓存

1.查询缓存

Mybatis提供了一级缓存,和二级缓存来减轻数据压力,提高数据库性能,Mybatis默认支持一级缓存,但二级缓存需要通过配置开启

2.一级缓存

2.1. 一级缓存是SqlSession级别的缓存,在操作数据库时需要创建SqlSession,其内部有一个HashMap数据结构用于存储缓存数据,多个SqlSession之间缓存数据区域是分隔开的

2.2.查询数据时,会先去一级缓存中看否存在对应的数据,如果没有,再去数据库查询,找到后返回数据的同时还会将数据保存到一级缓存中但如果有,就直接从一级缓存返回数据(这是查询流程)

2.3.注意!一旦执行commit操作,一级缓存就会被清空,以保证查询到最新的数据,关闭SqlSession时,一级缓存也会被清空

2.4.将Mybatis和Spring整合后,事务控制在service中,每个service方法在开始执行时会开启事务并创建SqlSession对象,在方法结束时关闭SqlSession。在同一个方法中多次调用的mapper方法共用一个一级缓存

3.二级缓存

3.1. 二级缓存是Mapper级别的缓存,每一个namespace有一个缓冲区域,一个Mapper的sql语句可以由多个SqlSession去操作,所以多个SqlSession共用二级缓存, 即二级缓存是跨SqlSession的。

3.2.开启二级缓存时,不但需要在SqlMapConfig.xml中开启,还需要在mapper.xml中开启,mapper存储缓冲数据的数据结构也是HashMap

<!--开启二级缓存(该内容在Mybatis核心配置文件中配置)-->
    <setting name="cacheEnabled" value="true"/> 

----------<!--格式问题,这是两个不同文件中的内容,不知道为什么分开生成代码块博客结构会乱,难道说是内容太少?如果有有知道的小伙伴请留言解答-->

<mapper namespace="cn.itcast.mapper.UserMapper">
    <!--开启本namespace的二级缓存,(该内容在Mapper映射文件中配置)-->
    <cache/>

3.3.由于二级缓存数据的存储介质多种多样,不一定只在内存中,为了能够将缓存数据取出执行反序列化操作,需要pojo实现序列化接口

3.4.在关闭SqlSession时,才能把SqlSession中的数据存储到二级缓存中,与一级缓存一样,一旦执行commit操作,二级缓存也会被清空,并且两者查询原理相同

3.5.二级缓存的参数

    <!--禁止当前select语句的二级缓存,useCache的默认值为true,即使用二级缓存-->
    <select id="findUserById" resultType="user" useCache="false">

    ···

    <!--不刷新缓存,insert、delete、update的flushCache默认值为true,即在执行commit()方法后清空缓存-->
    <update id="updateUserSex" parameterType="int" flushCache="false">

3.6. Mybatis整合Encache

在不使用分布式缓存时,缓存数据在各个服务器单独存取,不方便系统开发,所以需要使用分布式缓存对缓存数据进行集中管理。

Mybatis无法实现分布式管理,需要和其它的分布式缓存框架进行整合。

Mybatis提供了一个cache接口,其默认实现类是PerpetualCache,在Mybatis和Encache整合后,只需要更改Mybatis的默认实现类即可。

<!--需要添加encache.xml配置文件,并引入jar包:encache-core、mybatis-encache(这一小块儿内容在mapper映射文件中配置)-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

----------<!--同样的格式问题-->

encache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <diskStore path="F:\develop\ehcache" />
    <defaultCache 
        maxElementsInMemory="1000" 
        maxElementsOnDisk="10000000"
        eternal="false" 
        overflowToDisk="false" 
        timeToIdleSeconds="120"
        timeToLiveSeconds="120" 
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

12. Mybatis和Spring整合

需要先在先前的基础上还需要导入spring与mybatis整合的jar包和spring的jar包,并引入spring的核心配置文件

项目结构
applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.2.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

    <context:property-placeholder location="classpath:db.properties" />

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

    <!--配置SqlSessionFactory,该类在mybatis与spring的整合包下-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 加载mybatis的配置文件 -->
        <property name="configLocation" value="mybatis/SqlMapConfig.xml" />
        <property name="dataSource" ref="dataSource" />
    </bean>
</beans>

1.原始开发Dao方式

<mapper namespace="test">
    <select id="findUserById" parameterType="int" resultType="cn.itcast.ssm.po.User">
        SELECT * FROM USER WHERE id=#{id}
    </select>
</mapper>
public interface UserDao {

public User findUserById(int id) throws Exception;
}

//这里继承SqlSessionDaoSupport,它类似于Hibernate框架的HibernateDaoSupport
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {

@Override
public User findUserById(int id) throws Exception {
return getSqlSession().selectOne("test.findUserById", id);
}
}

<bean id="userDao" class="cn.itcast.ssm.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

public class UserDaoImplTest {

@Test
public void testFindUserById() throws BeansException, Exception {
System.out.println(((UserDao)new ClassPathXmlApplicationContext("spring/applicationContext.xml").getBean("userDao")).findUserById(1));
}
}

2.Mapper代理开发

<mapper namespace="cn.itcast.ssm.mapper.UserMapper">
    <select id="findUserById" parameterType="int" resultType="user">
        SELECT * FROM USER WHERE id=#{value}
    </select>
</mapper>
public interface UserMapper {

    public User findUserById(int id) throws Exception;
}

    <!--该类可根据Mapper接口生成代理对象-->
    <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">      
        <!--需要指定Mapper接口-->
        <property name="mapperInterface" value="cn.itcast.ssm.mapper.UserMapper"/>
        <!--该代理对象类继承SqlSessionDaoSupport,所以也需要注入sqlSessionFactory-->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>

public class UserMapperTest {

    @Test
    public void testFindUserById() throws BeansException, Exception {
        System.out.println(((UserMapper)new ClassPathXmlApplicationContext("spring/applicationContext.xml").getBean("userMapper")).findUserById(1));
    }
}

升级!

<!--mapper批量扫描:该类是一个Mapper扫描器,它能从指定包中扫描出所有mapper接口,并自动创建代理对象,扫描出来的mapper的bean的id为接口名称首字母小写-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定需要被扫描的包,不能使用通配符,只能用","将多个包隔开-->
        <property name="basePackage" value="cn.itcast.ssm.mapper"/>
        <!--由于扫描器会比加载配置文件的组件先执行,导致找不到sqlSessionFactory,所以这里使用sqlSessionFactoryBeanName和value属性-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
</beans>

    <!--这时,这一步也可以省略了,但仍然要求映射文件和Mapper接口在同一个包下,且名称相同-->
 <mappers>
        <package name="cn.itcast.ssm"/>
    </mappers>

13. Mybatis逆向工程

针对数据库单表自动生成mybatis执行所需要的代码,这里使用java程序的xml文件的方式单独创建项目来生成代码,生成的代码尽量不要修改!需要时拷贝!

需要先创建这个逆向工程的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <context id="testTables" targetRuntime="MyBatis3">
        <commentGenerator>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true" />
        </commentGenerator>

        <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
            connectionURL="jdbc:mysql:///mybatis" userId="root"
            password="123">
        </jdbcConnection>

        <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和 NUMERIC 类型解析为java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!-- targetProject:生成PO类的位置 -->
        <javaModelGenerator targetPackage="cn.itcast.ssm.po"
            targetProject=".\src">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
            <!-- 从数据库返回的值被清理前后的空格 -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!-- targetProject:mapper映射文件生成的位置 -->
        <sqlMapGenerator targetPackage="cn.itcast.ssm.mapper" 
            targetProject=".\src">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>

        <!-- targetPackage:mapper接口生成的位置 -->
        <javaClientGenerator type="XMLMAPPER"
            targetPackage="cn.itcast.ssm.mapper" 
            targetProject=".\src">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>

        <!-- 指定数据库表 -->
        <table tableName="items"></table>
        <table tableName="orders"></table>
        <table tableName="orderdetail"></table>
        <table tableName="user"></table>
    </context>
</generatorConfiguration>

再执行这段程序

public class GeneratorSqlmap {

    @Test
    public void generator() throws Exception{
        try{
            List<String> warnings = new ArrayList<String>();
            boolean overwrite = true;
            File configFile = new File("config/generatorConfig.xml"); 
            ConfigurationParser cp = new ConfigurationParser(warnings);
            Configuration config = cp.parseConfiguration(configFile);
            DefaultShellCallback callback = new DefaultShellCallback(overwrite);
            MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
                    callback, warnings);
            myBatisGenerator.generate(null);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

生成结果(是不是很好用!这些文件中的代码内容可比你想象中更加丰富,如果你还没用过逆向工程的话)。
生成的项目结构

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
毕业设计,基于SpringBoot+Vue+MySQL开发的公寓报修管理系统,源码+数据库+毕业论文+视频演示 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本公寓报修管理系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理人员提高事务处理效率,达到事半功倍的效果。此公寓报修管理系统利用当下成熟完善的Spring Boot框架,使用跨平台的可开发大型商业网站的Java语言,以及最受欢迎的RDBMS应用软件之一的MySQL数据库进行程序开发。公寓报修管理系统有管理员,住户,维修人员。管理员可以管理住户信息和维修人员信息,可以审核维修人员的请假信息,住户可以申请维修,可以对维修结果评价,维修人员负责住户提交的维修信息,也可以请假。公寓报修管理系统的开发根据操作人员需要设计的界面简洁美观,在功能模块布局上跟同类型网站保持一致,程序在实现基本要求功能时,也为数据信息面临的安全问题提供了一些实用的解决方案。可以说该程序在帮助管理者高效率地处理工作事务的同时,也实现了数据信息的整体化,规范化与自动化。 关键词:公寓报修管理系统;Spring Boot框架;MySQL;自动化;VUE
毕业设计,基于SpringBoot+Vue+MySQL开发的社区医院管理系统,源码+数据库+毕业论文+视频演示 信息数据从传统到当代,是一直在变革当中,突如其来的互联网让传统的信息管理看到了革命性的曙光,因为传统信息管理从时效性,还是安全性,还是可操作性等各个方面来讲,遇到了互联网时代才发现能补上自古以来的短板,有效的提升管理的效率和业务水平。传统的管理模式,时间越久管理的内容越多,也需要更多的人来对数据进行整理,并且数据的汇总查询方面效率也是极其的低下,并且数据安全方面永远不会保证安全性能。结合数据内容管理的种种缺点,在互联网时代都可以得到有效的补充。结合先进的互联网技术,开发符合需求的软件,让数据内容管理不管是从录入的及时性,查看的及时性还是汇总分析的及时性,都能让正确率达到最高,管理更加的科学和便捷。本次开发的社区医院管理系统实现了病例信息、字典表、家庭医生、健康档案、就诊信息、前台、药品、用户、用户、用户表等功能。系统用到了关系型数据库中王者MySql作为系统的数据库,有效的对数据进行安全的存储,有效的备份,对数据可靠性方面得到了保证。并且程序也具备程序需求的所有功能,使得操作性还是安全性都大大提高,让社区医院管理系统更能从理念走到现实,确确实实的让人们提升信息处理效率。 关键字:社区医院管理系统;信息管理,时效性,安全性,MySql
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值