Mybatis原理及使用

Mybatis

 

配置:

1、引入mybatis-3.1.1.jar

2、新建conf.xml放到src下

conf.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="development">

        <environment id="development">

            <transactionManager type="JDBC" />

            <dataSource type="POOLED">

                <property name="driver" value="com.mysql.jdbc.Driver" />

                <property name="url" value="jdbc:mysql://localhost:3306/mybatis" />

                <property name="username" value="root" />

                <property name="password" value="123456" />

            </dataSource>

        </environment>

    </environments>

    <mappers>

        <mapper resource="com/test/userMapper.xml" />

    </mappers>

</configuration>

其中environments是环境,可以包含多个environment,默认使用default属性对应的。

transactionManager是事务管理配置,可以是JDBC或者MANAGED。

JDBC表示事务用jdbc来管理

MANAGED表示事务交给spring等容器管理。

 

dataSource type="POOLED"是使用连接池。

 

mappers指定映射文件的位置。

3、创建实体类

package com.test;

 

public class User {

 

    private Integer id;

    

    private String name;

    

    private Integer age;

 

    public Integer getId() {

        return id;

    }

 

    public void setId(Integer id) {

        this.id = id;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public Integer getAge() {

        return age;

    }

 

    public void setAge(Integer age) {

        this.age = age;

    }

      

    

}

 

4、创建映射文件

/MybatisDemo/src/com/test/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">

<mapper namespace="com.test.userMapper">

    <select id="queryUser" parameterType="int" resultType="com.test.User">

        select * from users where id=#{aaa}

    </select>

</mapper>

其中#{aaa}是占位符,由于只有一个参数,所以名字可以任意取,这里是aaa。

 

5、客户端调用

package com.test;

 

import java.io.InputStream;

 

import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;

import org.apache.ibatis.session.SqlSessionFactoryBuilder;

 

public class Test {

 

    public static void main(String[] args) {

        // 取得conf.xml总体配置文件的输入流

        InputStream is = Test.class.getClassLoader().getResourceAsStream("conf.xml");

        // 构建sessionFactory

        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);

        // 取得session

        SqlSession sqlSession = sessionFactory.openSession();

        // statement对应userMapper.xml中的namesapce+标签id

        String statement = "com.test.userMapper.queryUser";

        // 查询

        User user = sqlSession.selectOne(statement, 1);

        System.out.println(user.getName());    // 输出Tom

    }

}

 

其中statement也可以是"queryUser" 短名称,它会自动去找命名空间,但这样不太好,如果2个命名空间都有idqueryUser的标签,这里就会报错,因此还是建议使用全名。

增删改查操作

userMapper.xml配置:

    <!-- 新增 ,parameterType指定传入参数类型-->

    <insert id="addUser" parameterType="com.test.User">

        insert into users(name, age)

        values (#{name}, #{age})

    </insert>

    

    <!-- 更新 ,parameterType指定传入参数类型-->

    <update id="updateUser" parameterType="com.test.User">

        update users

        set name=#{name},age=#{age}

        where id=#{id}

    </update>

    

    <!-- 删除,parameterType指定传入参数类型-->

    <delete id="deleteUser" parameterType="int">

        delete from users

        where id=#{id}

    </delete>

    

    <!-- 查询列表,resultType指定返回列表的元素类型-->

    <select id="queryAllUsers" resultType="com.test.User">

        select * from users

    </select>

增删改查的标签对应不同的sql语句,其实标签只是为了可读性,运行的时候只会看里面的语句,不会因为是update标签就一定执行update操作,例如update标签里面可以使insert语句,执行insert操作。标签起一定的检查作用,例如update标签就必须配置parameterType属性,否则运行报错。

对应测试代码:

    @Test

    public void testInsert() {

        SqlSession sqlSession = MybatisUtils.getFactory().openSession();

        String statement = "com.test.userMapper.addUser";

        User user = new User();

        user.setName("hello");

        user.setAge(99);

        sqlSession.insert(statement, user);

        sqlSession.commit();

    }

    

    @Test

    public void testUpdate() {

        SqlSession sqlSession = MybatisUtils.getFactory().openSession();

        String statement = "com.test.userMapper.updateUser";

        User user = new User();

        user.setName("test2");

        user.setAge(105);

        user.setId(2);

        sqlSession.update(statement, user);

        

        sqlSession.commit();

    }

    

    @Test

    public void testDelete() {

        SqlSession sqlSession = MybatisUtils.getFactory().openSession();

        String statement = "com.test.userMapper.deleteUser";

        sqlSession.delete(statement, 4);

        sqlSession.commit();

    }

    

    @Test

    public void testQueryAllUsers() {

        SqlSession sqlSession = MybatisUtils.getFactory().openSession();

        String statement = "com.test.userMapper.queryAllUsers";

        List<User> users = sqlSession.selectList(statement);

        System.out.println(users);

    }

其中sqlSession.commit();是提交事务,openSession()取得的session默认不自动提交事务,如果是openSession(true)会自动提交

 

使用注解方式(很少用,只用于简单查询的情况)

1、创建接口

package com.test;

 

import java.util.List;

 

import org.apache.ibatis.annotations.Delete;

import org.apache.ibatis.annotations.Insert;

import org.apache.ibatis.annotations.Select;

import org.apache.ibatis.annotations.Update;

 

public interface UserMapper {

 

    @Insert("insert into users(name, age) values (#{name}, #{age})")

    public int insertUser(User user);

    

    @Update("update users set name=#{name},age=#{age} where id=#{id}")

    public int updateUser(User user);

    

    @Delete("delete from users where id=#{id}")

    public int deleteUser(int id);

    

    @Select("select * from users")

    public List<User> queryAllUsers();

    

    @Select("select * from users where id=#{aaa}")

    public User queryById(int id);

}

 

2、conf.xml引入

<mapper class="com.test.UserMapper"/>

 

3、测试代码

    @Test

    public void testAnnotation() {

        SqlSession sqlSession = MybatisUtils.getFactory().openSession();

        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        System.out.println(userMapper.queryAllUsers());

    }

 

其实就是用UserMapper接口代替了xml文件。

 

用log4j打印Sql语句等日志

1、引入log4j-1.2.16.jar

2、src下新建log4j.xml(也可以是log4j.properties),内容:

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">

        <layout class="org.apache.log4j.PatternLayout">

            <param name="ConversionPattern"

                value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />

        </layout>

    </appender>

    <logger name="java.sql">

        <level value="debug" />

    </logger>

    <logger name="org.apache.ibatis">

        <level value="debug" />

    </logger>

    <root>

        <level value="debug" />

        <appender-ref ref="STDOUT" />

    </root>

</log4j:configuration>

此时即可打印出sql语句:

 

用resultMap映射实体类属性与表字段名

如果数据库表字段名与实体类属性名不一致,则需要建立映射,一种方法是select语句中为字段起别名,如:

select user_id as id from users

 

另一种是使用mybatis的resultMap映射,如:

 

    <!-- 查询列表,resultType指定返回列表的元素类型-->

    <select id="queryAllUsers" resultMap="UserResultMap">

        select * from users

    </select>

    

    <!-- 映射实体类属性名与表字段名 -->

    <resultMap type="User" id="UserResultMap">

        <id property="id" column="user_id"/>

        <result property="name" column="user_name"/>

        <result property="age" column="user_age"/>

    </resultMap>

其中resultMap="UserResultMap"resultMap标签的id对应。

resultMap标签内部的id标签和result标签可以互换,用id或者result只是为了方便阅读,一般id就是主键。

用association标签处理一对一、多对一封装(封装对象属性)

association在resultMap标签内,用于封装实体类属性的映射,当实体对象间有引用关系时,可以用association处理,如:

实体类

public class Teacher {

 

    private int id;

    

    private String name;

 

    public int getId() {

        return id;

    }

 

    public void setId(int id) {

        this.id = id;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

}

 

public class Classes {

 

    private int id;

    

    private String name;

    

    private Teacher teacher;

 

    public int getId() {

        return id;

    }

 

    public void setId(int id) {

        this.id = id;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public Teacher getTeacher() {

        return teacher;

    }

 

    public void setTeacher(Teacher teacher) {

        this.teacher = teacher;

    }

}

这里Classes类有Teacher属性。

关联表查询方式(也叫嵌套结果)

通过关联表查询,一条sql查询出结果,如:

映射文件配置:

    <select id="queryClasses" resultMap="ClassesResultMap">

        select * from class a,teacher b

        where a.teacher_id=b.t_id

    </select>

    

    <!-- 映射实体类属性名与表字段名 -->

    <resultMap type="Classes" id="ClassesResultMap">

        <id property="id" column="c_id"/>

        <result property="name" column="c_name"/>

        <association property="teacher" javaType="Teacher">

            <id property="id" column="t_id"/>

            <result property="name" column="t_name"/>

        </association>

    </resultMap>

其中association用于封装对象属性,property是属性名称,javaType是封装后的类型,需要跟property属性的类型一致。

association内部的id和result就是Teacher的属性,association就相当于Classes中Teacher属性的映射。

多次查询方式(也叫嵌套查询)

先查询出Classes信息,然后再查Classes对应的Teacher信息,如:

 

    <select id="queryTeacher" parameterType="int" resultMap="TeacherResultMap">

        select * from teacher where t_id=#{id}

    </select>

    

    <select id="queryClasses" resultMap="ClassesResultMap">

        select * from class

    </select>

    

    <!-- 映射实体类属性名与表字段名 -->

    <resultMap type="Classes" id="ClassesResultMap">

        <id property="id" column="c_id"/>

        <result property="name" column="c_name"/>

        <association property="teacher" column="teacher_id" select="queryTeacher">

            <id property="id" column="t_id"/>

            <result property="name" column="t_name"/>

        </association>

    </resultMap>

    <resultMap type="Teacher" id="TeacherResultMap">

        <id property="id" column="t_id"/>

        <result property="name" column="t_name"/>

    </resultMap>

 

这里的select="queryTeacher"去找queryTeacher标签查询Teacher,传入column="teacher_id"对应的值(column指定的相当于外键字段)

 

用collection标签处理一对多封装(封装List、Set等属性)

实体类

public class Classes {

 

    private int id;

    

    private String name;

    

    private List<Student> students;

    

    public int getId() {

        return id;

    }

 

    public void setId(int id) {

        this.id = id;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public List<Student> getStudents() {

        return students;

    }

 

    public void setStudents(List<Student> students) {

        this.students = students;

    }

 

 

public class Student {

 

    private int id;

    

    private String name;

 

    public int getId() {

        return id;

    }

 

    public void setId(int id) {

        this.id = id;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

}

 

Classes中有个List<Student>属性。

关联表查询方式(也叫嵌套结果)

Mapper配置:

    <select id="queryClasses" resultMap="ClassesResultMap">

        select * from class c, student s

        where c.c_id=s.class_id

    </select>

    

    <!-- 映射实体类属性名与表字段名 -->

    <resultMap type="Classes" id="ClassesResultMap">

        <id property="id" column="c_id"/>

        <result property="name" column="c_name"/>

        <collection property="students" ofType="Student">

            <id property="id" column="s_id"/>

            <result property="name" column="s_name"/>

        </collection>

    </resultMap>

 

其中collection属性表示需要映射的属性是个集合,ofType="Student"表示集合元素的类型,需要跟property="students"对应的属性元素类型一致

这里虽然查询语句会查询出多条,但会根据classes分组,自动把多条classes相同的存到同一个classes对象,然后多条的Students封装成list。

多次查询方式(也叫嵌套查询)

Mapper配置:

 

    <select id="queryClasses" resultMap="ClassesResultMap">

        select * from class

    </select>

    

    <select id="queryStudent" parameterType="int" resultMap="StudentResultMap">

        select * from student where s_id=#{id}

    </select>

    

    <!-- 映射实体类属性名与表字段名 -->

    <resultMap type="Classes" id="ClassesResultMap">

        <id property="id" column="c_id"/>

        <result property="name" column="c_name"/>

        <collection property="students" column="c_id" select="queryStudent">

            <id property="id" column="s_id"/>

            <result property="name" column="s_name"/>

        </collection>

    </resultMap>

    

    <resultMap type="Student" id="StudentResultMap">

        <id property="id" column="s_id"/>

        <result property="name" column="s_name"/>

    </resultMap>

这种也是发多次查询,其中column="c_id"是传给子查询的参数

多对多关系处理

多对多可以转换为两个一对多,因为只会根据一方查了另一个方。

使用关联表查询然后用一对多关系处理即可,如:

表student:

表class

关联表class_student

映射配置:

<mapper namespace="com.test3.mapper">

    

    <select id="queryClasses" resultMap="ClassesResultMap">

        select a.id, a.name as className, c.id as studentId, c.name as studentName

        from class a

        left join class_student b on a.id=b.class_id

        left join student c on c.id=b.student_id

                

    </select>

    

    <!-- 映射实体类属性名与表字段名 -->

    <resultMap type="Classes" id="ClassesResultMap">

        <id property="id" column="id"/>

        <result property="name" column="className"/>

        <collection property="students" ofType="Student">

            <id property="id" column="studentId"/>

            <result property="name" column="studentName"/>

        </collection>

    </resultMap>

    

</mapper>

用left join时,即使class表的每条记录即使在class_student没有对应,查询结果中也至少会有一条class的记录,只是student的list是空。

like查询

mapper.xml配置:

 

    <select id="queryUser" parameterType="ConditionUser" resultType="User">

        select * from users

        where

        name like #{name}

    </select>

传参:

 

        ConditionUser conditionUser = new ConditionUser();

        String name = "o";

        conditionUser.setName("%" + name + "%");

        List<User> userList = session.selectList("com.testlike.mapper.queryUser", conditionUser);

        System.out.println("userList=" + userList);

百分号写在了实际参数里。

Mybatis使用存储过程

使用很少,暂不研究

Mybatis一级、二级缓存

mybatis支持一级、二级缓存,一级缓存基SqlSession,二级缓存基于配置文件,增加<cache/>即可开启二级缓存,不过使用很少,暂不研究

 

Mybatis整合spring

1、添加jar包:

【mybatis】

        mybatis-3.2.0.jar

        mybatis-spring-1.1.1.jar

        log4j-1.2.17.jar

【spring】

        spring-aop-3.2.0.RELEASE.jar

        spring-beans-3.2.0.RELEASE.jar

        spring-context-3.2.0.RELEASE.jar

        spring-core-3.2.0.RELEASE.jar

        spring-expression-3.2.0.RELEASE.jar

        spring-jdbc-3.2.0.RELEASE.jar

        spring-test-3.2.4.RELEASE.jar

        spring-tx-3.2.0.RELEASE.jar

 

        aopalliance-1.0.jar

        cglib-nodep-2.2.3.jar

        commons-logging-1.1.1.jar

【MYSQL驱动包】

        mysql-connector-java-5.0.4-bin.jar

 

或者maven配置:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

 

    <groupId>com.test</groupId>

    <artifactId>mybatisspring</artifactId>

    <version>0.0.1-SNAPSHOT</version>

    <packaging>jar</packaging>

 

    <name>mybatisspring</name>

    <url>http://maven.apache.org</url>

 

    <properties>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <spring.version>4.2.6.RELEASE</spring.version>

    </properties>

 

    <dependencies>

        <dependency>

            <groupId>junit</groupId>

            <artifactId>junit</artifactId>

            <version>4.12</version>

            <scope>test</scope>

        </dependency>

 

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-test</artifactId>

            <version>${spring.version}</version>

            <scope>test</scope>

        </dependency>

 

        <dependency>

            <groupId>org.mybatis</groupId>

            <artifactId>mybatis</artifactId>

            <version>3.3.1</version>

        </dependency>

        <dependency>

            <groupId>mysql</groupId>

            <artifactId>mysql-connector-java</artifactId>

            <version>5.1.39</version>

        </dependency>

 

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-jdbc</artifactId>

            <version>${spring.version}</version>

        </dependency>

 

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-context-support</artifactId>

            <version>${spring.version}</version>

        </dependency>

 

        <dependency>

            <groupId>org.mybatis</groupId>

            <artifactId>mybatis-spring</artifactId>

            <version>1.2.4</version>

        </dependency>

    </dependencies>

</project>

2、创建实体类,如:

public class User {

 

    private int id;

    private String name;

    private Date birthday;

    private double salary;

 

//set,get方法

}

创建表:

CREATE TABLE s_user(

    user_id INT AUTO_INCREMENT PRIMARY KEY,

    user_name VARCHAR(30),

    user_birthday DATE,

    user_salary DOUBLE

)

3、创建Dao的接口

public interface UserMapper {

 

    public int save(User user);

    

    public int update(User user);

    

    public int delete(int id);

    

    public User findById(int id);

    

    public List<User> findAll();

}

4、创建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">

<mapper namespace="com.dmall.mapper.UserMapper">

    

    <insert id="save" parameterType="User">

        insert into s_user(user_name, user_birthday, user_salary)

        values (#{name}, #{birthday}, #{salary})

    </insert>

    

    <update id="update" parameterType="User">

        update s_user

        set user_name=#{name}, user_birthday=#{birthday}, user_salary=#{salary}

        where user_id=#{id}

    </update>

    

    <delete id="delete" parameterType="int">

        delete from s_user

        where user_id=#{id}

    </delete>

    

    <select id="findById" parameterType="int" resultType="User">

        select user_id id, user_name name, user_birthday birthday, user_salary salary

        from s_user

        where user_id=#{id}

    </select>

    

    <select id="findAll" resultType="User">

        select user_id id, user_name name, user_birthday birthday, user_salary salary

        from s_user

    </select>

</mapper>

其中的namespace必须与接口的全名一致,标签的id必须跟接口中的方法名一致。

5、创建spring配置文件spring_config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:context="http://www.springframework.org/schema/context"

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

        http://www.springframework.org/schema/context/spring-context-3.2.xsd

        http://www.springframework.org/schema/tx

        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">

        

    <!-- 1.配置datasource -->

    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

        <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>

        <property name="username" value="root"/>

        <property name="password" value="123456"/>

        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>

    </bean>

    

    <!-- 2.配置mysqlSqlSessionFactory -->

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

        <property name="dataSource" ref="datasource"/>

        <property name="typeAliasesPackage" value="com.dmall.domain"></property>

    </bean>

    

    <!-- 3.配置事务管理器 -->

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

        <property name="dataSource" ref="datasource"/>

    </bean>

    

    <!-- 4.使用注解式事务 -->

    <tx:annotation-driven transaction-manager="txManager"/>

    

    <!-- 5.配置扫描mapper映射文件的类MapperScannerConfigurer -->

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

        <property name="basePackage" value="com.dmall.mapper"/>

        <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>

    </bean>

    

</beans>

其中第5步是让spring自动扫描包下的mybatis的mapper配置文件,通过反射dao的接口类自动创建实现。如果不用spring自动创建dao的实现(接口绑定的方式),则可以不用配置第5步。

如果不使用事务,3、4也可以不配。

这里使用的是注解式事务(@Transactional加到方法上)。

如果使用声明式事务,则需要配置3和aop、tx标签,如:

    <aop:config proxy-target-class="true">

        <aop:pointcut id="txServiceMethods" expression="execution(* com.dmall.ledger.service..*.*(..))" />

        <aop:advisor advice-ref="txAdvice" pointcut-ref="txServiceMethods" />

    </aop:config>

 

    <tx:advice id="txAdvice" transaction-manager="txManager">

        <tx:attributes>

            <tx:method name="*" propagation="REQUIRED" />

        </tx:attributes>

    </tx:advice>

6、创建测试类

package com.dmall.test;

 

import java.util.Date;

 

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

 

import com.dmall.domain.User;

import com.dmall.mapper.UserMapper;

 

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("/spring_config.xml")

public class TestS {

 

    @Autowired

    UserMapper userMapper;

    

    @Test

    public void testAdd() {

        User user = new User();

        user.setName("hello33");

        user.setBirthday(new Date());

        user.setSalary(55555);

        userMapper.save(user );

    }

}

其中的@RunWith(SpringJUnit4ClassRunner.class)表示用spring的测试框架,这样可以不用手动去加载spring的xml配置。

@ContextConfiguration("/spring_config.xml")前面的斜杠表示src的根路径。

这样通过@Autowired就可以直接注入spring的对象进行测试,省了很多事。

 

 

7、说明

没有使用接口绑定方式时,mybatis整合spring其实就是将创建sqlSession时的SessionFactory交给了Spring管理,而创建SessionFactory需要数据源,因此配了一个DataSource。最后使用的是SqlSessionTemplate,它是SqlSession的实现类。

 

跟不使用spring取得SqlSession类似,只不过它是通过InputStream的方式通过读取配置文件取得数据源。

 

 

如果mybatis整合spring使用了接口绑定方式,则可以不用配置SqlSessionTemp,而且也不用conf.xml配置文件了,因为conf.xml中主要配置了数据库连接和mapper.xml文件路径,使用spring后数据库连接配置到了spring的datasource,而mapper.xml自动到了扫描包路径。

但一般不会将mapper.xml放到实体的包路径下,而是单独建一个目录专门放mapper.xml,因此一般情况下还是会有mybatis配置文件。

 

如果有其他特殊配置,也可以使用conf.xml里面的配置参数。

如以下的一些个性化配置:

 

此时启用conf.xml的方式是将路径配置到SessionFactory的configLocation属性:

 

附:测试用表sql

CREATE TABLE users(id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(20), age INT);

INSERT INTO users(NAME, age) VALUES('Tom', 12);

INSERT INTO users(NAME, age) VALUES('Jack', 11);

 

CREATE TABLE orders(

    order_id INT PRIMARY KEY AUTO_INCREMENT,

    order_no VARCHAR(20),

    order_price FLOAT

);

INSERT INTO orders(order_no, order_price) VALUES('aaaa', 23);

INSERT INTO orders(order_no, order_price) VALUES('bbbb', 33);

INSERT INTO orders(order_no, order_price) VALUES('cccc', 22);

 

CREATE TABLE teacher(

    t_id INT PRIMARY KEY AUTO_INCREMENT,

    t_name VARCHAR(20)

);

CREATE TABLE class(

    c_id INT PRIMARY KEY AUTO_INCREMENT,

    c_name VARCHAR(20),

    teacher_id INT

);

ALTER TABLE class ADD CONSTRAINT fk_teacher_id FOREIGN KEY (teacher_id) REFERENCES teacher(t_id);    

 

INSERT INTO teacher(t_name) VALUES('LS1');

INSERT INTO teacher(t_name) VALUES('LS2');

 

INSERT INTO class(c_name, teacher_id) VALUES('bj_a', 1);

INSERT INTO class(c_name, teacher_id) VALUES('bj_b', 2);

 

CREATE TABLE student(

    s_id INT PRIMARY KEY AUTO_INCREMENT,

    s_name VARCHAR(20),

    class_id INT

);

INSERT INTO student(s_name, class_id) VALUES('xs_A', 1);

INSERT INTO student(s_name, class_id) VALUES('xs_B', 1);

INSERT INTO student(s_name, class_id) VALUES('xs_C', 1);

INSERT INTO student(s_name, class_id) VALUES('xs_D', 2);

INSERT INTO student(s_name, class_id) VALUES('xs_E', 2);

INSERT INTO student(s_name, class_id) VALUES('xs_F', 2);

 

create table d_user(

    id int primary key auto_increment,

    name varchar(10),

    age int(3)

);

 

insert into d_user(name,age) values('Tom',12);

insert into d_user(name,age) values('Bob',13);

insert into d_user(name,age) values('Jack',18);

 

CREATE TABLE c_user(

    id INT PRIMARY KEY AUTO_INCREMENT,

    NAME VARCHAR(20),

    age INT

);

INSERT INTO c_user(NAME, age) VALUES('Tom', 12);

INSERT INTO c_user(NAME, age) VALUES('Jack', 11);

 

CREATE TABLE s_user(

    user_id INT AUTO_INCREMENT PRIMARY KEY,

    user_name VARCHAR(30),

    user_birthday DATE,

    user_salary DOUBLE

)

 

其他

conf.xml总体配置

mappers映射文件路径配置

可以配置包路径或者文件绝对路径,如:

        <mapper resource="com/test/userMapper.xml" />

        <mapper url="file:///D:/aaa/userMapper.xml" />

        <mapper url="file:///home/kkk/userMapper.xml" />

 

使用properties文件

可以将数据库连接等配置信息放入properties文件,xml引入它,然后用$符号引用变量,如:

db.properties内容:

driver=com.mysql.jdbc.Driver

url=jdbc:mysql://localhost:3306/mybatis

name=root

password=123456

 

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

    <properties resource="db.properties"/>

    

    <environments default="development">

        <environment id="development">

            <transactionManager type="JDBC" />

            <dataSource type="POOLED">

                <property name="driver" value="${driver}" />

                <property name="url" value="${url}" />

                <property name="username" value="${name}" />

                <property name="password" value="${password}" />

            </dataSource>

        </environment>

    </environments>

    <mappers>

        <mapper resource="com/test/userMapper.xml" />

    </mappers>

</configuration>

 

其中    <properties resource="db.properties"/>

是引用文件,${driver}对应properties的driver属性值。

 

properties可以有property属性,用于补充properties文件中不存在的属性,如果property属性跟properties文件有相同的,优先取properties文件的,如:

    <properties resource="db.properties">

        <property name="password" value="123453"/>

        <property name="hello" value="hello123"/>

    </properties>

hello是新增的属性

password此时还是优先取properties文件的password=123456

也可以在代码中直接设置Properties的值,如:

Properties properties = new Properties();

properties.setProperty("name", "aaa");

SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader, properties);

 

此时代码中的设置的name会以最高优先级的方式覆盖掉properties文件的name配置。

优先级:

代码指定参数>properties文件配置>xml的property标签配置。

Settings配置全局参数

typeAliases起别名

可以为实体类起别名:

如:

conf.xml的configuration下增加:

    <typeAliases>

        <typeAlias type="com.test.User" alias="User"/>

    </typeAliases>

userMapper.xml中:

 

    <select id="queryUser" parameterType="int" resultType="User">

        select * from users where id=#{aaa}

    </select>

其中resultType的User就代表了com.test.User

 

也可以为某一个包下的所有类启别名,如:

    <typeAliases>

        <package name="com.test"/>

    </typeAliases>

此时会默认使用该包下所有的"类名"取代"包名.类名"

 

默认提供的别名:


如:

 

    <select id="queryUserName" parameterType="int" resultType="string">

        select name from users where id=#{aaa}

    </select>

这里用resultType="string"、resultType="String"、resultType="java.lang.String"都可以。

typeHandler自定义类型转换

数据库类型和java类型的转换都是通过实现TypeHandler接口的类实现的,如:

可以自定义类型处理器覆盖它的配置:

自定义类型处理器需要实现TypeHandler接口,继承TypeReference类。

如:

 

import java.sql.CallableStatement;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

 

import org.apache.ibatis.type.JdbcType;

import org.apache.ibatis.type.TypeHandler;

import org.apache.ibatis.type.TypeReference;

 

public class ExampleTypeHandler extends TypeReference<String> implements TypeHandler<String> {

 

    public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {

        // TODO Auto-generated method stub

        

    }

 

    public String getResult(ResultSet rs, String columnName) throws SQLException {

        // TODO Auto-generated method stub

        return "a1";

    }

 

    public String getResult(ResultSet rs, int columnIndex) throws SQLException {

        // TODO Auto-generated method stub

        return "b2";

    }

 

    public String getResult(CallableStatement cs, int columnIndex) throws SQLException {

        // TODO Auto-generated method stub

        return "c3";

    }

}

 

然后在conf.xml中配置:

<configuration>

    <properties resource="com/test/config.properties" />

    <typeHandlers>

        <typeHandler handler="com.test.handler.ExampleTypeHandler" />

    </typeHandlers>

    <environments default="development">

        <environment id="development">

            <transactionManager type="JDBC" />

            <dataSource type="POOLED">

                <property name="driver" value="${driver}" />

                <property name="url" value="${url}" />

                <property name="username" value="${name}" />

                <property name="password" value="${password}" />

            </dataSource>

        </environment>

    </environments>

    <mappers>

        <mapper resource="com/test/userMapper.xml" />

    </mappers>

 

</configuration>

 

此时再查询就会调用到自定义的类型处理器:

有现成的BaseTypeHandler抽象类实现了实现TypeHandler接口,继承了TypeReference类,可以继承BaseTypeHandler抽象类就行了。

 

ps:此种方式对全局所有的转换有效,如果只需要对单个sql查询结果进行转换,需要用resultMap标签配置,具体待研究。

environments配置多个环境

environments可配置多个数据库连接,如开发环境、生产环境,默认使用default属性指定的。构建SqlSessionFactory时可以手动指定,如:

有一个develop和一个product环境,默认使用development的。但代码中可以手动指定成product的:

如:

// 构建sessionFactory

SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader, "product");

 

TracsanctionManager、Datasource待确定

使用接口绑定的方式

User user = sqlSession.selectOne("com.test.UserMapper.queryUser", 1);

这种查询方式可以使用接口来代替。

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

User user = userMapper.queryUser(1);

 

UserMapper接口:

public interface UserMapper {

    public User queryUser(int id);

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

<mapper namespace="com.test.UserMapper">

    <select id="queryUser" parameterType="int" resultType="com.test.User">

        select * from users where id=#{aaa}

    </select>

</mapper>

这里sqlSession会根据UserMapper.class类名的全路径com.test.UserMapper去匹配xml文件中的namespace,调用queryUser时会反射出方法名去匹配xml文件中的id="queryUser",因此namespace和方法名必须完全一致,否会报错。

如:

 

这样做的好处是不用写namespace防止拼写错误。相当于把xml中的namespace和id映射到了接口全路径名和方法名上。然后sqlSession通过接口名和方法去xml中找对应。

范围和生命周期

SqlSessionFactoryBuilder:

创建完SqlSessionFactory后即可销毁。

 

SqlSessionFactory:

一直保留,跟web应用同生命周期,使用单例方式。

 

SqlSession

一个请求线程一个SqlSession,或者每次操作数据库都创建,它是线程不安全的。

 

Mapper

映射器的接口实例,如:

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

建议每次都创建。

参数绑定

默认会生成preparedStatement语句,使用#{id},会作为参数替代?问号,而用${id}则会直接生成到语句中,不会作为参数。因此动态传入表名、字段名等时用$而不用#,因为表名、字段名是不能作为preparedStatement的参数的。

 

参数别名见前面的"typeAliases起别名"。

select标签

属性列表:

select标签的resultMap或者resultType必须有一个,表名返回类型如何封装,如果是map,则返回结果的key是字段名,value是字段值。如果有as,则key是as的别名。

insert、update、delete

属性列表

 

insert返回数据库生成的主键id

使用insert标签的useGeneratedKeys、属性即可,如:

这里的keyProperty="id"中的id对应的是User实体类的属性名字(其实是setId方法)。

userGeneratedKeys默认是false,也就是不会返回主键id。

 

返回的主键id也可以放入map中,如:

sql标签定义重复使用的代码段

如:

动态sql

可以用类似于jstl的if、when、foreach等标签动态拼装sql语句

标签语法同jstl相同,如支持=、!=、eq、neq等

具体参见api文档:

 

if标签

如:

    <select id="queryUser" parameterType="ConditionUser" resultType="User">

        select * from users

        where

        1=1

        <if test="name != null and name != 'null'">

            and name = #{name}

        </if>

    </select>

 

 

注意:Integer类型的参数,会认为0也等于空串,如:

 

此时age传入0时,会认为跟''相等,进而不加where条件,因此对Integer类型判断时不要加age != ''条件,而只加age!=null即可排除空,否则会把0也一起排除。

 

choose标签

如:

where标签

用于解决拼接sql的问题,如:

当name和age都为空时,如果不用where标签就会报sql错。

当name为空,age不为空时,不用where标签也会报sql错。

where标签会判断如果后面没有条件,就不加where关键字,如果后面条件以and、or开头,就把它去掉。

set标签

与where类似,用于解决sql拼接问题,set用在update里,如:

它会自动去掉多余的逗号

 

foreach标签

用于循环,如:

传入参数是一个map,foreach的collection通过"idsList"这个key取出map中的list,然后遍历。其中separator指定分隔符。item指定遍历的单个元素名字。

 

使用foreach标签时,如果只传入一个参数时,可以直接传入list或者数组,不用封装到map等里面,此时maybatis会自动把list放到一个map中,key为list,数组的key为array,如:

此时直接将ids这个list传了进去。

 

open和close属性指定前缀和后缀,如:

等价于

遍历Map

如:

    <update id="update" parameterType="SyncdataVo">

        update ${syncdataVo.tableName} set

<foreach collection="syncdataVo.optMap.keys" item="key" separator="," >

${key}=#{syncdataVo.optMap[${key}]}

</foreach>

where id=#{syncdataVo.id}

    </update>

 

这里遍历传入map的key、value对应更新字段值。key作为字段名,value作为更新值。

源码分析

读取配置,创建Configuration,创建SessionFactory

Reader reader = Resources.getResourceAsReader("conf.xml");

SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);

首先读取reader中的配置,封装成configuration。创建sessionFactory时,会将configuration传给sessionFactory。

 

创建SqlSession,传递configuration

SqlSession sqlSession = sessionFactory.openSession();

创建SqlSession时,会将configuration传给SqlSession。

 

基于Mapper方式的获取和回调

获取Mapper

 

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

从SqlSession中获取Mapper,其实调用了configuration的getMapper。

 

获取mapper的过程:

configuration会去调用MapperRegistry. getMapper方法。

 

 

MapperRegistry通过目标接口名字找出MapperProxyFactory,然后调用MapperProxyFactory.newInstance创建。

ps: knownMappers是一个HashMap,key是接口类Class,value是MapperProxyFactory对象。

通过接口类Class为key找到对应的MapperProxyFactory,对应的MapperProxyFactory中就含有对应的接口。

 

 

MapperProxyFactory创建InvocationHandler的实现类MapperProxy的对象,然后根据目标接口创建jdk动态代理对象。

执行接口方法时回调MapperProxy的invoke方法

MapperProxy的invoke是接口方法被执行执行时,mybatis统一回调的入口,创建MapperProxy时会传入sqlSession、目标接口类,因此回调的时候可以根据目标接口类的方法到配置文件中找到对应的配置执行语句,然后通过sqlSession执行增删改查语句。

 

最后也是通过sqlSession来执行,如:

 

这里的动态代理回调跟平时用的有些差别,它没有目标实现类target,因此不会回调目标方法,而是根据目标方法名字直接联合configuration配置直接执行处理逻辑(mapperMethod.execute方法)。

 

ps:invoke区分了如果是从Object继承来的方法,如toString()、hashCode()、equals()等,则不走mapperMethod.execute方法,而是直接回调。目标对象是this,也就是MapperProxy的对象。因此这里生成的代理对象mapper的toString()返回的是MapperProxy类路径而不是通常的目标实现类路径。

 

 

 

SqlSession的操作流程

不论是否采用Mapper接口方式,最后增删改查都是通过SqlSession来调用的,如:

未采用Mapper接口方式:

采用Mapper接口方式:

 

SqlSession接口定义了各种增删改查操作的入口:

下面以selectList为例分析它的执行过程:

SqlSession取得MappedStatement,调用Executor的query

配置文件封装成configuration后,每个xml中的select、update、insert、delete等标签都会封装成一个MappedStatement对象,在configuration中以statement字符串(如:com.test.user.selectUser)作为key保存在configuration中,使用时通过key来获取对应的MappedStatement对象。

Executor生成BoundSql,创建StatementHandler

 

 

 

Executor调用StatementHandler创建Statement,封装参数

最后交给TypeHandler设置参数。

Executor调用StatementHandler执行query

 

ResultSetHandler封装返回结果

将ResultSet转换成List返回。

Mybatis中使用的设计模式

CachingExecutor中使用了委托模式和装饰器模式,将所有操作都委托给一个成员变量SimpleExector去操作,而它在外面做了一层封装(装饰)。

posted @ 2020-12-16 10:39  吴克兢  阅读(128)  评论(0)    收藏  举报