MyBatis学习

本文详细介绍了MyBatis的CRUD操作及对象关系映射,包括many2one、one2many关系的处理,以及如何使用association和collection进行复杂关系的映射。同时,文中展示了如何开启延迟加载策略并解决N+1问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MyBatis学习【二】CRUD测试

更新时间:2013年07月17日09时27分 来源:传智播客成都中心
 
    既然Mybatis也是一种ORM框架,所以,肯定也有两类配置文件。第一类,用来配置MyBatis环境,比如数据库连接等。第二类,用来配置对象到数据库表的映射。在MyBatis中,不光要配置对象到数据库表的映射,包括对应的SQL,也需要自己来完成(相当于自己来完成hibernate生成对象CRUDsql的过程要自己来完成)。在学习mybatis的过程当中,要对比着hibernate来思考,可以更容易的理解。而且,mybatis在ibatis的基础上面,更多的吸收了标准ORM的一些思想,所以,在API的设计上面和Hibernate还是有相似的地方,可以辅助学习。
    首先任意创建一个对象User:

package cd.itcast.mybatis.domain;
    public class User {
        private Long id;
        private String name;
        private Date hireDate;
    //getter & setter
    }


下面就使用Mybatis完成对这个对象的CRUD的测试。
    首先创建一个Mybatis的配置文件,我们在classpath下面创建一个xml文件,任意起名为mybatis-config.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>
        <environments default="default">
            <environment id="default">
                <transactionManager type="JDBC" />
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql:///mybatis"/>
                    <property name="username" value="root"/>
                    <property name="password" value="admin"/>
                </dataSource>
            </environment>
        </environments>
    </configuration>


这就是mybatis的一个典型的配置文件。在这个配置文件中,我们可以清楚得看到一些关键信息。第一,有一个environments这个元素,在这个元素里面配置的是一个一个的environment元素,每一个environment元素有一个id做为名字,而environments元素上面有一个default属性,引用environment元素的名称,很容易想象出他们之间的关系。在environments里面可以定义多个enviroment元素,使用default属性指定一个默认的environment。而在environment里面,定义了一组数据库相关的东西,包括transactionManager,这个元素代表着怎么去管理事务,后面还会详细讲到。而下面的dataSource就很明显了,代表连接数据库的相关信息。在这里,dataSource有一个属性type,这里我们写的是POOLED,很明显,使用连接池来做mybatis的连接提供。在        dataSource里面,配置了四个属性,做为数据库连接信息配置。
    接下来,就是为我们的User对象完成映射了。前面说了,在mybatis中,需要自己去控制sql,所以,我们的配置文件会像下面这样。在User的同级包下,添加一个XML文件,起名为UserMapper.xml,代表这个文件是User对象的映射文件:


<?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="cd.itcast.mybatis.domain.UserMapper">
        <insert id="save" keyProperty="id" parameterType="cd.itcast.mybatis.domain.User"useGeneratedKeys="true">
            INSERT INTO user(name,hiredate) values (#{name},#{hireDate})
        </insert>
    </mapper>


Mapper:代表这是一个对象的映射关系
    namespace:为当前映射关系创建的命名空间,要引用这个映射关系里面定义的东西,需要带上这个命名空间。还有其他非常重要的作用,之后会看到。


    insert:代表定义了一个插入操作(即SQL的insert操作)
    id:为这个插入操作起一个名字,以后要保存user这个对象,其实就是要调用这个插入操作。
    keyProperty:代表主键对应对象的属性名称。有点像hibernate里面那个id元素。
    parameterType:mybatis里面非常重要的一个元素,代表这个insert操作对应的方法需要传入一个什么类型的对象。这里,我们就是要把User这个对象保存到数据库中,所以,我们这个插入方法的参数就是一个User类型的对象实例。
    useGeneratedKeys:代表告诉mybatis,使用autoGeneratedKey来获取数据库帮我们自动生成的ID。(这个方法在jdbc中讲过)
    INSERT INTO user(name,hiredate) values (#{name},#{hireDate}):非常重要的东西。这就是一条完整的把USER对象插入到user表的SQL语句。但是在这个SQL语句里面,最重要的就是#{name},#{hireDate}这两个占位参数。这两个参数的意思就是:使用传入对象(parameterType)中的name,hireDate两个属性的值来填充这两个参数内容。
完成映射文件之后,在mybatis-config.xml中加入映射文件:


<mappers>
        <mapper resource="cd/itcast/mybatis/domain/UserMapper.xml"/>
    </mappers>


插入操作的测试。1,首先创建一个USER表:


CREATE TABLE `user` (
        `id` bigint(11) NOT NULL AUTO_INCREMENT,
        `name` varchar(50) DEFAULT NULL,
        `hiredate` date DEFAULT NULL,
        PRIMARY KEY (`id`)
    )


然后创建一个UserTest类:


@Test
    public void testSave() throws Exception{
        SqlSessionFactory factory= new SqlSessionFactoryBuilder().build(Resources .getResourceAsStream("mybatis-config.xml"));
        SqlSession session = factory.openSession();
        try {
            User u = new User();
            u.setName("itcasT");
            u.setHireDate(new Date());
            session.insert("cd.itcast.mybatis.domain.UserMapper.save", u);
            session.commit();
        } finally {
            session.close();
        }
    }


1,Resources .getResourceAsStream("mybatis-config.xml"):使用Reources类的静态方法getResourceAsStream,从classpath:mybatis-config.xml位置读入配置文件,并装成inputStream。
    2,SqlSessionFactory factory= new SqlSessionFactoryBuilder().build(inputStream):使用读入的流,创建SqlSessionFactory。可以从名字上类比hibernate。
    3,SqlSession session = factory.openSession();使用factory开启一个SqlSession。SqlSession即是在mybatis中操作实体对象的主要的类了。
    4,User u = new User();创建一个需要保存的实体类。
    5,session.insert("cd.itcast.mybatis.domain.UserMapper.save", u):非常重要的方法,使用session的insert方法,对比上面的映射文件,即告诉mybatis现在调用的是一个标记到insert元素中的SQL语句,接着传入cd.itcast.mybatis.domain.UserMapper.save,很明显这个就是namespace+id拼成的,意思就是我要调用的是定义在cd.itcast.mybatis.domain.UserMapper这个映射关系中的名字为save的insert语句。这个语句需要传入一个cd.itcast.mybatis.domain.User对象实例做为参数,所以把创建的User实体做为参数传入。
    6,session.commit():很明显,即提交事务。
    7,session.close():和hibernate一样,使用完后,都必须要关闭session。
    从整个代码上面可以很明显的猜测到mybatis的创建过程。执行测试,保存成功。但是看不到mybatis执行的sql,怎么办?日志:
    在classpath中建立一个名叫log4j.properties的文件,内容如下:


# Global logging configuration
    log4j.rootLogger=ERROR, stdout
    # MyBatis logging configuration...
    log4j.logger.cd.itcast.mybatis=TRACE
    # Console output...
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n


很明显,log4j.logger.cd.itcast.mybatis意思就是要监控cd.itcast.mybatis包下面的所有mapper的动作。再次运行:


DEBUG [main] - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@79a2e7]
    DEBUG [main] - ==>  Preparing: INSERT INTO user(name,hiredate) values (?,?) 
    DEBUG [main] - ==> Parameters: itcasT(String), 2012-09-12 15:10:16.812(Timestamp)


可以很清楚的看到执行的sql和执行sql使用的参数。

MyBatis学习【三】CRUD测试之修改操作

更新时间:2013年07月18日15时17分 来源:传智播客成都中心

    修改操作,在UserMapper.xml中加入:
    <update id="update" parameterType="cd.itcast.mybatis.domain.User">
        UPDATE user SET name = #{name},hiredate = #{hireDate} WHERE id = #{id}
    </update>

    1, update:代表当前定义了一个update操作(sql的update语句);
    a) id:为当前操作定义了一个名字,用于调用
    b) parameterType:update调用需要一个cd.itcast.mybatis.domain.User的类型实例做为参数。
    2, UPDATE user SET name = #{name},hiredate = #{hireDate} WHERE id = #{id}:即完整的UPDATE语句,其中的#{name},#{hireDate},#{id}和insert语句中的完全一致。

    修改测试:
    @Test
    public void testUpdate() {
        SqlSessionFactory factory= new SqlSessionFactoryBuilder().build(Resources .getResourceAsStream("mybatis-config.xml"));
        SqlSession session = factory.openSession();
        try {
            User u = new User();
            u.setId(1l);
            u.setName("update");
            u.setHireDate(new Date());
            session.update("cd.itcast.mybatis.domain.UserMapper.update", u);
            session.commit();
        } finally {
        session.close();
        }
    }

    对照保存测试,很好理解,关键的一句:
    session.update("cd.itcast.mybatis.domain.UserMapper.update", u):即调用在cd.itcast.mybatis.domain.UserMapper中定义的名字为update的Update语句,传入一个cd.itcast.mybatis.domain.User的对象实例。
    运行测试,console打印:
    DEBUG [main] - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@1b60280]
    DEBUG [main] - ==>  Preparing: UPDATE user SET name = ?,hiredate = ? WHERE id = ? 
    DEBUG [main] - ==> Parameters: update(String), 2012-09-07 00:00:00.0(Timestamp), 2(Long)
 

MyBaits学习【四】CRUD测试之Get方法

更新时间:2013年07月19日09时12分 来源:传智播客成都中心

    Get方法:
    在UserMapper.xml中添加如下配置:
    <select id="get" parameterType="long" resultType="cd.itcast.mybatis.domain.User">
        SELECT * FROM user WHERE id = #{id}
    </select>

    1, select:代表这是一个SELECT语句
    a) parameterType:代表要执行这个select语句需要传入一个类型为long的参数,即User对象的id
    b) resultType:非常重要的东西,即完成ORM的映射关系所在。这里指定的cd.itcast.mybatis.domain.User代表,把结果集转换成一个User对象实例。注意,在这里有一个要求就是,属性名称和列的名称必须一致,才能完成转型。否则就需要自定义属性到字段的映射规则。
    2, SELECT * FROM user WHERE id = #{id}:即查询语句。非常重要的一点,这个#{id}是mybatis在使用查询单个对象的时候默认使用的参数的名称。即传入的类型为long的值,mybatis就用#{id}来代替。

    Get测试:
    @Test
    public void testGet() {
        SqlSessionFactory factory= new SqlSessionFactoryBuilder().build(Resources .getResourceAsStream("mybatis-config.xml"));
        SqlSession session = factory.openSession();
        try {
            User u = session.selectOne("cd.itcast.mybatis.domain.UserMapper.get",1l);
            System.out.println(u);
        } finally {
        session.close();
        }
    }

    其中很重要的一点:
    User u = session.selectOne("cd.itcast.mybatis.domain.UserMapper.get",1l);
    调用的是cd.itcast.mybatis.domain.UserMapper中定义的get对应的SELECT语句。注意这里使用的是session.selectOne,代表,选择的是一个。那么mybatis就会把查询到的结果集中的第一行数据按照列名和属性名一一对应的关系,把结果包装成一个User类型的实例。
    从这里也能得出结论:在mapper文件中定义的sql语句的类型和在session中使用的调用方法是对应的。
    运行测试:
    DEBUG [main] - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@1b60280]
    DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE id = ? 
    DEBUG [main] - ==> Parameters: 2(Long)
    TRACE [main] - <==    Columns: id, name, hiredate
    TRACE [main] - <==        Row: 2, itcasT, 2012-09-07

    还可以看到返回的结果集内容。

Mybatis学习【五】CRUD测试之多条查询

更新时间:2013年07月22日09时38分 来源:传智播客成都中心

    多条查询:
    在UserMapper.xml中加入:
    <select id="list" resultType="cd.itcast.mybatis.domain.User">
        SELECT * FROM user
    </select>

    1,非常重要的一点,这次返回的结果应该是一个List<User>,但是配置的resultType仍然是cd.itcast.mybatis.domain.User,mybatis强大的一点,对于select,其实我们配置的都是把每一条结果集转成的对象类型。
    2,SELECT * FROM user:即查询语句。因为不需要参数了,所以也没有定义paramterType。

    查询测试:
    @Test
    public void testList() {
        SqlSessionFactory factory= new SqlSessionFactoryBuilder().build(Resources .getResourceAsStream("mybatis-config.xml"));
        SqlSession session = factory.openSession();
        try {
            List<User> us = session.selectList("cd.itcast.mybatis.domain.UserMapper.list");
            System.out.println(us);
            } finally {
            session.close();
        }
    }

    1, 使用的是session.selectList,代表这次调用的select语句需要把结果集包装成一个对象列表。
    运行测试,console:
    DEBUG [main] - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@158f9d3]
    DEBUG [main] - ==>  Preparing: SELECT * FROM user 
    DEBUG [main] - ==> Parameters: 
    TRACE [main] - <==    Columns: id, name, hiredate
    TRACE [main] - <==        Row: 2, update, 2012-09-07
    TRACE [main] - <==        Row: 3, itcasT, 2012-09-12

    得到正确的结果。

MyBatis学习【六】CRUD测试之删除

更新时间:2013年07月23日09时31分 来源:传智播客成都中心

    删除:
    在UserMapper.xml中添加:
    <delete id="delete" parameterType="long">
        DELETE FROM user WHERE id = #{id}
    </delete>

    使用delete标识这是一个删除语句。参数parameterType=long表明删除数据的参数。同select一样,如果传入的参数就是一个id,那么在sql语句中使用#{id}来引用这个参数。
    测试用例:
    @Test
    public void testDelete() {
        SqlSessionFactory factory= new SqlSessionFactoryBuilder().build(Resources .getResourceAsStream("mybatis-config.xml"));
        SqlSession session = factory.openSession();
        try {
            session.delete("cd.itcast.mybatis.domain.UserMapper.delete", 1l);
            session.commit();
        } finally {
                session.close();
        }
    }

    使用session.delete方法指定调用的删除SQL和传入参数。运行结果:
    DEBUG [main] - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@158f9d3]
    DEBUG [main] - ==>  Preparing: DELETE FROM user WHERE id = ? 
    DEBUG [main] - ==> Parameters: 1(Long)


    到这里,mybatis的CRUD基本演示完毕。从这些方法里面大概能够感受到mybatis的一些基本的使用方式。lanMyBatis学习【六】CRUD测试之删除
    删除:
    在UserMapper.xml中添加:
    <delete id="delete" parameterType="long">
        DELETE FROM user WHERE id = #{id}
    </delete>

    使用delete标识这是一个删除语句。参数parameterType=long表明删除数据的参数。同select一样,如果传入的参数就是一个id,那么在sql语句中使用#{id}来引用这个参数。
    测试用例:
    @Test
    public void testDelete() {
        SqlSessionFactory factory= new SqlSessionFactoryBuilder().build(Resources .getResourceAsStream("mybatis-config.xml"));
        SqlSession session = factory.openSession();
        try {
            session.delete("cd.itcast.mybatis.domain.UserMapper.delete", 1l);
            session.commit();
        } finally {
               session.close();
        }
    }

    使用session.delete方法指定调用的删除SQL和传入参数。运行结果:
    DEBUG [main] - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@158f9d3]
    DEBUG [main] - ==>  Preparing: DELETE FROM user WHERE id = ? 
    DEBUG [main] - ==> Parameters: 1(Long)


    到这里,mybatis的CRUD基本演示完毕。从这些方法里面大概能够感受到mybatis的一些基本的使用方式。

MyBatis学习【七】CRUD示例的优化上

更新时间:2013年07月24日09时23分 来源:传智播客成都中心

    前几天我们讲了CRUD的全部测试,下面是对CRUD示例的优化:
    1,typeAliases:在示例中的UserMapper.xml文件中可以看到,凡是使用到User类型的时候,都需要写User的类的全限定名。在mybatis-config.xml中,可以使用typeAliases元素来简化这个操作:
    在mybatis-config.xml中添加:
    <typeAliases>
        <typeAlias type="cd.itcast.mybatis.domain.User" alias="User"/>
    </typeAliases>

    即为cd.itcast.mybatis.domain.User起了一个简化的名字:User,那么之后在Mapper文件中使用User就可以代替cd.itcast.mybatis.domain.User,想想hibernate的import。
    修改的UserMapper.xml文件如下:
    <mapper namespace="cd.itcast.mybatis.domain.UserMapper">
        <insert id="save" keyProperty="id" parameterType="User" useGeneratedKeys="true">
            INSERT INTO user(name,hiredate) values (#{name},#{hireDate})
        </insert>

        <update id="update" parameterType="User">
            UPDATE user SET name = #{name},hiredate = #{hireDate} WHERE id = #{id}
        </update>

        <delete id="delete" parameterType="int">
            DELETE FROM user WHERE id = #{id}
        </delete>

        <select id="get" parameterType="int" resultType="User">
            SELECT * FROM user WHERE id = #{id}
        </select>

        <select id="list" resultType="User">
            SELECT * FROM user
        </select>
    </mapper>

    简化不少。

    2, properties:我们说过数据库的连接信息一般要放到一个额外的.properties文件中,mybatis允许我们这样做。
    首先,修改mybatis-config.xml文件:
    <environments default="default">
        <environment id="default">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${driverClass}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    联想spring的dataSource配置方式,不难理解。
    占位符的数据来源配置有三种方式:
    第一种使用方式:在SqlSessionFactoryBuilder的build方法中,还提供了额外传入Properties对象的方法:
    public SqlSessionFactory build(InputStream inputStream, Properties properties)
    这个方法后面的Properties对象就可以做为mybatis-config.xml中的参数来源。所以,我们可以这样来使用:
    Properties p=new Properties();
    p.load(this.getClass().getClassLoader().getResourceAsStream(“db.properties”);
    并在classpath下定义一个db.properties文件:
    driverClass=com.mysql.jdbc.Driver
    url=jdbc:mysql:///hibernate
    username=root
    password=admin



    第二种使用方式:在mybatis-config.xml中有properties这样一个标签,那么我们可以在mybatis-config.xml中定义:
    <properties>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql:///hibernate"/>
        <property name="username" value="root"/>
        <property name="password" value="admin"/>
    </properties>

    即可。

    第三种方式:在mybatis-config.xml中的properties元素中,引入外部的properties文件:
    <properties resource="db.properties" />
    并在classpath中添加db.properties文件即可。
    第三种方式和第二种方式可以混用,即:
    <properties resource="org/mybatis/example/config.properties">
          <property name="username" value="dev_user"/>
          <property name="password" value="F2Fa3!33TYyg"/>
    </properties>


    三者的优先级为:代码传入的Properties > resource加载的Properties > properties元素中定义的property。

MyBatis学习【八】CRUD示例的优化下

更新时间:2013年07月25日09时30分 来源:传智播客成都中心

继续昨天的CRUD示例的优化:

3,MyBatisUtil的抽象:
在mybatis中,SqlSessionFactory和SqlSession的作用和地位极其类似SessionFactory和Session,包括其线程安全性。即
SqlSessionFactory是线程安全的,在整个应用中对应一个数据源只需要创建一个。
SqlSession是线程不安全的,生命周期最好是method或者线程。
所以,我们就能抽象一个MyBatisUtil来提供SqlSession的创建:
public class MyBatisUtil {
    private static SqlSessionFactory factory;

    static {
        try {
            factory = new SqlSessionFactoryBuilder().build(Resources
            .getResourceAsStream("mybatis-config.xml"));
        } catch (Exception e) {
            e.printStackTrace();
            }
        }

    public static SqlSession openSession() {
    return factory.openSession();
    }
}


4,Mapper接口的实现
在上面的测试用例中,在调用session的方法的时候,都会传入要调用的SQL的namespace+id名称,这不是必须的。可以只传入id即可。但是,如果在mybatis的环境中有多个相同id的映射名称,就会报错。所以,一般情况下,调用方法最好还是使用namespace+id。但是,namespace+id的使用方式很容易报错,因为是string类型的,没有检查。所以,mybatis提供了一种非常好的设计方式来避免这种问题,即Mapper接口。
对照UserMapper.xml的定义,我们只需要创建一个接口,注意,接口的名字和包必须和mapper.xml定义的namespace一致,即创建一个接口:cd.itcast.mybatis.domain.UserMapper:
package cd.itcast.mybatis.domain;
public interface UserMapper {
    //对应<insert id="save" keyProperty="id" parameterType="User">
    void save(User u);

    //对应<update id="update" parameterType="User">
    void update(User u);

    //对应<delete id="delete" parameterType="long">
    void delete(Long id);

    //对应<select id="get" parameterType="long" resultType="User">
    User get(Long id);

    //对应<select id="list" resultType="User">
    List<User> list();
}

对照每一个方法的注释,应该是很好理解的。
使用Mapper接口。有了Mapper接口,并且Mapper接口放在了指定的位置之后,我们的测试就可以写成:
public class CRUDTest {
@Test
public void testSave() {
    SqlSession session = MyBatisUtil.openSession();
    try {
        User u = new User();
        u.setName("itcasT");
        u.setHireDate(new Date());
        UserMapper mapper=session.getMapper(UserMapper.class);
        mapper.save(u);
        session.commit();
    } finally {
        session.close();
    }
}

@Test
public void testGet() {
    SqlSession session = MyBatisUtil.openSession();
    try {
        UserMapper mapper=session.getMapper(UserMapper.class);
        User u=mapper.get(2l);
        System.out.println(u);
    } finally {
        session.close();
    }
}

@Test
public void testUpdate() {
    SqlSession session = MyBatisUtil.openSession();
    try {
        User u = new User();
        u.setId(1l);
        u.setName("update");
        u.setHireDate(new Date());
        UserMapper mapper=session.getMapper(UserMapper.class);
        mapper.update(u);
        session.commit();
    } finally {
        session.close();
    }
}

@Test
public void testList() {
    SqlSession session = MyBatisUtil.openSession();
    try {
        UserMapper mapper=session.getMapper(UserMapper.class);
        List<User> us= mapper.list();
        System.out.println(us);
    } finally {
        session.close();
    }
}

@Test
public void testDelete() {
    SqlSession session = MyBatisUtil.openSession();
    try {
        UserMapper mapper=session.getMapper(UserMapper.class);
        mapper.delete(2l);
        session.commit();
    } finally {
        session.close();
    }
}
}

代码变得非常清晰,并且类型都使用接口强制性的完成。
Mybatis怎么做的?
测试:
@Test
public void testMapper(){
    SqlSession session = MyBatisUtil.openSession();
    try {
        UserMapper mapper=session.getMapper(UserMapper.class);
        System.out.println(mapper.getClass().getName());
    } finally {
        session.close();
    }
}

打印结果:
$Proxy4
很简单了,mybatis为接口做了一个动态代理。在执行UserMapper接口上面的方法时,参考接口的全限定名,即可找到对应的UserMapper.xml,在执行接口上面的每一个方法的时候,实际上就是在执行namespace+id,mybatis在根据定义的方法的元素,选择调用合适的session的方法来执行,并传入参数可以。
使用Mapper接口的方式,在集成Spring+MyBatis也非常方便。因为我们可以直接把Mapper接口看作DOMAIN的DAO接口了。

假设现在User对象不变,但是对应USER表中的name列的名称DBA重新设计成了username,那现在再来运行一遍测试:
可以看到这次的name属性就为null。这时候就体现出mybatis在直接使用resultType的局限性,要求属性名称必须和列的名称一致(大小写可以不一致)。在这种情况下,就必须要手动来完成数据表列和对象的映射关系了。首先来修改get方法:
<select id="get" parameterType="int" resultMap="usermapping">
SELECT * FROM user WHERE id = #{id}
</select>
在这里,可以看到,去掉了resultType,因为resultType只能完成默认的对象类型的转换。在这里修改成了resultMap,其实这里叫做resultMapping对于学习了hibernate的童鞋来说更好理解。在这里我把结果集定义到了一个名字叫做usermapping的映射上。下面定义usermapping:
<resultMap type="User" id="usermapping">
    <id property="id" column="id"/>
    <result property="name" column="username"/>
    <result property="hireDate" column="hiredate"/>
</resultMap>

resultMap定义了一个ORM的具体映射方式。
1,type:代表O,即最终返回的对象类型
2,id:为该映射设置一个名称,这个名称就是在get或list中使用的resultMap对应的id
3,id/result:对应这属性的映射,可以参考hibernate的property。id和result的区别在于,id一般用于映射主键,可以提高速度,result一般对于普通的属性。
设置完成后,就可以将对象正常get了。
在mybatis中,其实如果仅仅只是使用了resultType,相当于mybatis自动的帮我们建立了一个resultMap,只是这个resultMap直接完成了属性和列相同名称的映射而已。所以,mybatis真正完成映射的地方就在resultMap。

到此,单对象的CRUD的Mybatis实现就完成了。可以看到,在mybatis中,大部分的控制细节仍然是交由我们自己去控制,特别是sql。所以,待会我们看到多对象关系的时候,一定要有这个概念,mybatis不会像hibernate那样为我们考虑周全对象的CRUD,所有的SQL都应该由我们自己去控制。所以,在完成mybaits对象关系的配置中,需要转变一些在hibernate中的固有的面向对象的思想。如果说hibernate是面向对象为主,关系为辅,那么在mybatis中则是着重考虑的是关系模型,换句话说,如果对象模型设计的不好,就会很容易的感觉到实现的难度。

MyBatis学习【九】many2one

更新时间:2013年07月26日09时53分 来源:传智播客成都中心

上一章我们讲到,如果说hibernate是面向对象为主,关系为辅,那么在mybatis中则是着重考虑的是关系模型,换句话说,如果对象模型设计的不好,就会很容易的感觉到实现的难度。

首先来看看最简单的单向many2one:
建立对象:
public class Customer {
    private Long id;
    private String name;
}

public class Orders {
    private Long id;
    private String sn;
    private Double price;
    private Customer customer;
}

很简单,Orders和Customer是一个单向的many2one的关系。和hibernate一样,我们先来配置简单的customer:
创建CustomerMapper.xml:
<mapper namespace="cd.itcast.mybatis.customer">
    <insert id="save" keyProperty="id" parameterType="Customer"
    useGeneratedKeys="true">
        INSERT INTO customer(name) values (#{name})
    </insert>

    <select id="get" resultType="Customer" parameterType="int">
        SELECT * FROM customer WHERE id = #{id}
    </select>
</mapper>

只是一个很简单的单对象映射操作。
在hibernate的many2one中我们知道,在many方的外键关联到one方的主键。在many方保存的时候,应该在对应外键保存关联one方的id。所以,many方的保存insert应该是这样:
OrdersMapper.xml:
<insert id="save" keyProperty="id" parameterType="Orders"
useGeneratedKeys="true">
    INSERT INTO orders(sn,price,customer_id) values (#{sn},#{price},#{customer.id})
</insert>

在这里看到,我们在对应外键(customer_id)的地方,使用#{customer.id}来代表orders的customer属性的id属性,mybatis会像EL那样自动帮我们得到值。所以,在这里一定要注意保存对象的顺序,必须是先保存one再保存many方。在hibernate中,如果保存顺序有误,hibernate会在提交事务的时候同步脏数据,帮我们补一条update语句来保证对象的关系正确,但是在mybaits中,所有的sql都由我们来完成,所以没人会帮我们来完成update。所以,对应的保存测试应该是:
@Test
public void testSave(){
    SqlSession session=MyBatisUtil.openSession();

    Customer c=new Customer();
    c.setName("itcasT");

    Orders o=new Orders();
    o.setPrice(800d);
    o.setSn("001");
    o.setCustomer(c);

    session.insert("cd.itcast.mybatis.customer.save",c);
    session.insert("cd.itcast.mybatis.orders.save",o);

    session.commit();
    session.close();
}


下面就是many方的获取了。如果只是简单的把orders配置成一个单对象,即使用resultType来映射:
<select id="get" parameterType="long" resultType="Orders">
    SELECT * FROM orders WHERE o.id = #{id}
</select>

如果是这样,在测试的时候:
@Test
public void testGet(){
    SqlSession session=MyBatisUtil.openSession();
    Orders o=session.selectOne("cd.itcast.mybatis.orders.get", 1l);
    System.out.println(o);
    Customer c=o.getCustomer();
    System.out.println(c);
    session.close();
}

会看到,orders对应的customer是null,只能说明mybatis不能帮我们自动完成相关的关系映射。下面就来看下在mybatis中的关系映射:
第一种方式:使用额外的sql:
<select id="get" parameterType="int" resultMap="ordersmap">
     SELECT * FROM orders WHERE o.id = #{id}
</select>

在这里,添加resultMap来完成相关的映射:
<resultMap type="Orders" id="ordersmap">
    <id property="id" column="id"/>
    <result property="sn" column="sn"/>
    <result property="price" column="price"/>
    <association property="customer" column="customer_id" javaType="Customer" 
    select="cd.itcast.mybatis.customer.get">
</resultMap>

可能第一次看这个配置会觉得很奇怪,其实很好理解:
1,association:在mybatis中不像hibernate那样,把关系分的很细致,比如one-to-one,many-to-one,而只是把关系分成了两种:第一种是对单对象的关系,就用association,一种是集合的关系,这个待会再看。因为这里orders只是把customer对象作为自己的一个属性,所以使用association来表示这个关系;
2,property:和hibernate一样,定义这个关系在orders对象上面的对应属性。在完成关联对象的映射后,会使用这个属性把对象设置到orders对象上;
3,column:代表从查询结果集(select * from orders where id = ?)中得到对关系维护的列,这里就指明的是外键customer_id;
4,javaType:代表完成映射后应该返回的对象类型,这里肯定就是Orders
5,select:是这种映射方式的最关键的地方,这个select指代着cd.itcast.mybatis.customer.get这条SQL,这条sql即是得到customer的sql,而这条sql需要传入一个customer的id,我们前面已经通过column="customer_id"指定了应该传给cd.itcast.mybatis.customer.get的值就是结果集中的customer_id。
所以,可以看出,mybatis在这种映射的方式为:首先执行SELECT * FROM orders WHERE o.id = #{id},然后对id,sn,price属性直接从结果集中得到;接着,从结果集中得到customer_id列对应的值,这个值即是orders对应customer对象的id,然后把这个值作为id交给cd.itcast.mybatis.customer.get去执行,而这个执行的结果会被拼装为一个Customer对象,接着这个对象再会被设置到orders的customer属性中。完成映射。
再次执行get测试:
DEBUG [main] - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@19b1de]
DEBUG [main] - ==>  Preparing: SELECT * FROM ORDERS WHERE id = ? 
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <==    Columns: id, sn, cust_id
TRACE [main] - <==        Row: 1, 001, 1
DEBUG [main] - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@19b1de]
DEBUG [main] - ==>  Preparing: SELECT * FROM CUSTOMER WHERE id = ? 
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <==    Columns: id, name
TRACE [main] - <==        Row: 1, c
Orders [id=1, sn=001]
Customer [id=1, name=c]

可以看到,正常查询得到结果。但是,可能会有点疑问,我们之前在学hibernate的时候,不是说延迟加载是一种比较好的处理对象的方式么?但是现在我们看到例子中的对象并没有延迟加载。因为默认情况下,mybatis不会延迟加载对象:
要开启延迟加载,需要给mybatis增加几个配置,在mybatis-config.xml中:
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

再次执行get测试:得到的结果和刚才一样,仍然感觉没有执行延迟加载。问题在哪里呢?其实mybatis已经完成了延迟加载,只是mybatis的延迟加载策略和hibernate的延迟加载策略不一样。Hibernate是把被加载的目标对象做成proxy对象提供延迟加载(即这里的Customer),而mybatis是把对象本身做成延迟加载对象,我们把get测试修改一下:
SqlSession session = MyBatisUtil.getInstance().openSession();
OrdersMapper om=session.getMapper(OrdersMapper.class);
Orders o=om.get(1l);
System.out.println(o.getClass());
session.close();

打印结果:
DEBUG [main] - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@19b1de]
DEBUG [main] - ==>  Preparing: SELECT * FROM ORDERS WHERE id = ? 
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <==    Columns: id, sn, cust_id
TRACE [main] - <==        Row: 1, 001, 1
class cd.itcast.mybatis.domain.Orders$$EnhancerByCGLIB$$39a00050
001

可以看到,orders对象却是是proxy对象,并且访问其他属性,不会发送sql。但是,只要得到customer,那么查询customer的sql就会立刻发送。简单来说,mybatis的延迟加载原则是,只要你去拿这个对象,表明你在用这个对象。注意和hibernate的区别。

第二种方式,也是mybatis推荐的方式。上面那种方式,容易产生N+1问题。所以,mybatis建议使用内联的映射方式。修改ordersmapping:
<resultMap type="Orders" id="ordersmapper">
    <id property="id" column="id"/>
    <result property="sn" column="sn"/>
    <association property="customer" javaType="Customer">
        <id property="id" column="cid"/>
        <result property="name" column="cname"/>
    </association>
</resultMap>
<select id="get" resultMap="ordersmapper" parameterType="long">
    SELECT c.id as cid,c.name as cname,o.* FROM
ORDERS o LEFT JOIN CUSTOMER c ON o.CUST_ID = o.id WHERE o.id = #{id}
</select>

第一,在select中使用left join的方式把orders和对应的customer一起查询出来,并且为customer设置对应的别名。
第二,修改resultMap,去掉select和column,而修改为内部的映射形式。
那么,当mybatis执行完成sql之后,会把响应的属性转换成Orders对象的属性,当解析到customer属性时,mybatis会使用内部的映射关系把结果集中对应的列转换成Customer对象。
再次运行get,执行结果相同。

再次说明,mybatis建议使用第二种方式来完成映射。有人又会考虑到,那么如果只是orders的下拉列表,在结果里面不需要customer,这条sql不是很浪费性能么?这里又要说到mybatis的另一个原则,按需配置。意思是,如果只是用作下拉列表,那么只需要再做一个select来专门为orders的下拉列表做筛选。

MyBatis学习【十】one2many

更新时间:2013年07月29日09时30分 来源:传智播客成都中心

上一章讲了many2one,接下来看看单向的one2many,请注意,下面的代码只做演示,不推荐在真实项目中使用。通过这个例子,也能更深刻的理解到在使用mybatiis的时候,应该更加谨慎的设计对象。
首先创建对象:
public class Employee {
    private Long id;
    private String name;
}
public class Department {
    private Long id;
    private String name;
    private Set<Employee> emps = new HashSet<Employee>();
}

可以看到department和employee是单向的one2many。
这时候,对于employee来说,就是一个简单的单对象,我们先完成Employee对象的映射:
<mapper namespace="cd.itcast.mybatis.mapper.EmployeeMapper">

    <insert id="save" parameterType="Employee" useGeneratedKeys="true"
keyProperty="id">
        INSERT INTO EMPLOYEE(name)
        VALUES
        (#{name})
    </insert>

    <select id="get" resultType="Employee" parameterType="long">
        SELECT * FROM
        EMPLOYEE WHERE id = #{id}
    </select>
</mapper>

接下来看看department的映射。先看保存。注意,之前我们说过,在mybatis中,所有的sql都是我们自己来控制。而现在的对象是单向的one2many,考虑在hibernate中,因为从many方没法管理one方的关系,只有one方能够管理关系,所以只有通过额外的update语句来完成关系的维护。所以,现在我们除了department本身的保存,还需要用一个额外的update来维护one和many的关系:
<insert id="save" parameterType="Employee" useGeneratedKeys="true"
keyProperty="id">
    INSERT INTO DEPARTMENT(name)
    VALUES
    (#{name})
</insert>

<update id="update" parameterType="hashmap">
    UPDATE EMPLOYEE SET DEPT_ID = #{deptId} WHERE id = #{id}
</update>

Insert没啥多说的,注意这个update,我们设置的parameterType是hashmap类型。下面的update语句中,使用了#{deptid}和#{id},在这里deptid和id就不再指代属性了,而是map中的key值了。
下面来看看save测试:
@Test
public void testSave() {
    Department d = new Department();
    d.setName("d");

    Employee e = new Employee();
    e.setName("e1");

    Employee e2 = new Employee();
    e2.setName("e2");

    d.getEmps().add(e);
    d.getEmps().add(e2);

    SqlSession session = MyBatisUtil.getInstance().openSession();

    EmployeeMapper em = session.getMapper(EmployeeMapper.class);
    em.save(e);
    em.save(e2);

    DepartmentMapper dm = session.getMapper(DepartmentMapper.class);
    dm.save(d);

    for (Employee te : d.getEmps()) {
    Map<String, Object> m = new HashMap<String, Object>();
    m.put("deptId", d.getId());
    m.put("id", te.getId());
    dm.update(m);
}

    session.commit();
    session.close();
}


一定要注意保存顺序,首先是保存many方的两个employee,接着保存one方,department,然后我们需要手动遍历department对应的employee列表,并创建成一个一个的hashmap,并将这些hashmap作为参数传入更新关系的update中。
从这个save就可以看出,如果对象设计有问题,那么在设计sql的时候就很困难。当然,还有其他的简单的保存方式,比如保存employee的时候使用hashmap保存,不使用Employee对象。
 

MyBatis学习【终】

更新时间:2013年07月30日09时10分 来源:传智播客成都中心

讲了many2one和one2many,下面来看看get方法。在之前已经说过,如果是映射单对象,直接使用association来映射。而如果关系是一个集合,则需要使用collection来描述。和association一样,mybatis不会去管关系是many2many还是one2many。同理要完成collection映射,也应该有两种方式,1,发送另一条sql;2,内联映射。
第一种配置方式:
首先在departmentMapper.xml中添加:
<select id="get" resultMap="departmentmapper" parameterType="long">
    SELECT * FROM department WHERE id =  #{id}
</select>

这只是查询出一条Department,接下来配置resultMap:
<resultMap type="Department" id="departmentmapper">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <collection property="emps" column="id" ofType="Employee" select="cd.itcast.mybatis.mapper.EmployeeMapper.gets" />
</resultMap>

这里配置了collection,在collection中:
1,property:代表集合对应的属性名称为emps;
2,column:代表one方的id(配合下面的select就清楚了)
3,ofType:注意,这里的ofType代表的是集合里面的对象类型
4,select:非常重要的一条,我们先看看这条select对应的映射:
<select id="gets" resultType="Employee" parameterType="long">
    SELECT * FROM EMPLOYEE WHERE DEPT_ID = #{id}
</select>

可以看到,这段sql根据外键从employee表中查询所有的匹配给定的department的employee对象。
结合select和column,再联想之前的association中的select方式,我们可以明白,mybatis的存放策略为:首先执行查询得到department结果集,从结果集中得到相关的列,拼装id和name属性。当mybatis运行到emps属性时,会从结果集中,把id得到,并作为参数传入cd.itcast.mybatis.mapper.EmployeeMapper.gets,并查询到该department对应的employees,最后把employees设置到Department的emps属性中。
注意这种方式也遵循延迟加载策略,并且容易产生N+1问题。

第二种配置方式,内联。
<select id="get" resultMap="departmentmapper" parameterType="long">
    SELECT d.*,e.id as eid,e.name as ename FROM
    DEPARTMENT d LEFT JOIN EMPLOYEE e ON e.DEPT_ID = d.id WHERE d.id = #{id}
</select>
    <resultMap type="Department" id="departmentmapper">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
   <collection property="emps" ofType="Employee">
    <id property="id" column="eid"/>
    <result property="name" column="ename"/>
</collection>
</resultMap>

结合前面的解释,应该很好理解了。

Mybatis学习到此结束,如果你有兴趣,可以参照学习!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值