mybatis总结

一、mybatis简介

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。 MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatement、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。

二、mybatis入门

1、使用jdbc操作数据库存在的问题

  • JDBC编程步骤:

    1. 加载数据库驱动

    2. 创建并获取数据库连接

    3. 创建jdbc statement对象

    4. 设置SQL语句

    5. 传递SQL语句参数

    6. 执行statement获取结果

    7. 解析SQL执行结果

    8. 释放资源

  • jdbc程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package cn.itheima.jdbc;
import java.sql.*;
public class JDBCTest {
public static void main(String[] args) {
Connection connection = null;
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", "linux");
//定义sql语句 ?表示占位符
String sql = "select * from user where username = ?";
//获取预处理statement
preparedStatement = connection.prepareStatement(sql);
//设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
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{
//释放资源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
  • jdbc存在的问题

    1. 数据库连接创建,释放频繁造成系统资源浪费从而影响系统性能,可以使用数据库连接池技术来解决这个问题。

    2. SQL语句在代码中硬编码,不便于维护,实际应用中修改SQL语句需要修改java代码,不便于修改。

    3. 使用preparedStatement语句向占位符传参存在硬编码,实际上SQL语句的where条件不固定,修改困难

    4. 对结果集存在硬编码(查询列名),SQL变化导致解析代码变化,系统不易维护,将数据库记录封装成pojo对象解析比较方便。

2、mybatis架构

Alt text

  1. mybatis配置

    • SqlMapConfig.xml为mybatis的全局配置文件,配置了mybatis的运行环境等信息。
    • mapper.xml为SQL映射文件,文件中配置了操作数据库的SQL语句,需要加载在SQLMapConfig.xml文件中。
  2. 通过mybatis环境等配置信息构造SqlSessioNFactory,也就是会话工厂。

  3. 由会话工厂创建SqlSession,操作数据库需要通过会话来实现。

  4. mybatis低层自定义了Executor执行器操作数据库,Execotor接口有两个实现,一个是基本执行器,一个是缓存执行器。

  5. MappedStatement也是mybatis的一个低层封装对象,它包装了mybatis配置信息及SQL映射信息等。mapper.xml文件中一个SQL对应一个MappedStatement对象,SQL的id即MappedStatement的id。

  6. MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。

  7. MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。

3、mybatis的入门程序

1、mybatis下载

mybatis的代码由GitHub管理,地址为:https://github.com/mybatis/mybatis-3/releases

文件结构为:

Alt text

2、实现的功能

  • 根据用户id查询一个用户信息

  • 根据用户名称模糊查询用户信息列表

  • 添加用户

  • 更新用户

  • 删除用户

3、工程搭建

  1. 使用IDEA 2017.2 创建Java工程,jdk为1.8.0_141

  2. 添加jar包,包括mybatis核心包,依赖包,数据库驱动包

    Alt text

  3. 日志的打印:mybatis默认使用log4j输出日志信息,在classpath下创建log4j.properties:

    1
    2
    3
    4
    5
    6
    # Global logging configuration
    log4j.rootLogger=DEBUG, stdout
    # 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
  4. SqlMapConfig.xml文件:配置了数据源,事务管理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?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">
    <!-- 使用jdbc事务管理-->
    <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>
    </environment>
    </environments>
    </configuration>
  5. po类的编写:po类作为mybatis进行SQL映射使用,po类通常与数据表对应,User.java如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package cn.itheima.pojo;
    import java.util.Date;
    import java.util.List;
    public class User {
    private int id;
    private String username;// 用户姓名
    private String sex;// 性别
    private Date birthday;// 生日
    private String address;// 地址
    //omit the getter and setter...
    }
  6. 编写SQL映射文件:User.xml, namespace用于隔离SQL语句

    1
    2
    3
    4
    5
    6
    7
    8
    <?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">
    <!--命名空间的作用为SQL隔离-->
    <mapper namespace="test">
    </mapper>
  7. 加载映射文件,将User.xml添加在SQLMapConfig.xml中:

    1
    2
    3
    <mappers>
    <mapper resource="User.xml"/>
    </mappers>

4、根据id查询用户信息

  1. 在user.xml中添加SQL语句:

    1
    2
    3
    4
    5
    6
    7
    8
    <!--根据id获取用户信息-->
    <!--parameterType指定传入参数类型,
    resultMap指定结果集类型
    #{}为占位符,如果传入的是基本类型,#{}中的变量名称可以随意指定
    -->
    <select id="findUserById" parameterType="java.lang.Integer" resultType="cn.itheima.pojo.User">
    select * from user where id = #{id}
    </select>
  2. 测试程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    private SqlSessionFactory sqlSessionFactory;
    @Before
    public void createSqlSessionFactory() throws IOException {
    //配置文件
    String resource = "SqlMapConfig.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    //使用SqlSessionFactoryBuilder从XML配置文件中创建
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }
    @Test
    public void testFindUserById() {
    SqlSession sqlSession = null;
    try {
    //创建会话
    sqlSession = sqlSessionFactory.openSession();
    //第一个参数,namespace+id
    User user = sqlSession.selectOne("test.findUserById", 1);
    System.out.println(user);
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    if (sqlSession != null) {
    sqlSession.close();
    }
    }
    }

5、根据用户名查询用户信息

  1. 在url.xml中添加SQL语句:

    1
    2
    3
    4
    5
    6
    <!--如果返回结果为集合,则可以调用session中的selectList方法,resultType仍然为-->
    <!--${}拼接符,如果传入的是基本类型,其内部的变量名称必须为value-->
    <!--拼接符有SQL注入的风险,慎重使用,-->
    <select id="findUserByUserName" parameterType="java.lang.String" resultType="cn.itheima.pojo.User">
    select * from user where username like '%${value}%'
    </select>
  2. 测试程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    private SqlSessionFactory sqlSessionFactory;
    @Before
    public void createSqlSessionFactory() throws IOException {
    //配置文件
    String resource = "SqlMapConfig.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    //使用SqlSessionFactoryBuilder从XML配置文件中创建
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }
    @Test
    public void testFindUserByUserName() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    List<User> users = sqlSession.selectList("test.findUserByUserName", "王");
    for (User user : users) {
    System.out.println(user);
    }
    }

6、概念比较

  1. #{}和${}

    • #{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止SQL注入。#{}可以接收简单类型值或pojo属性值。如果parameterType传输单个简单类型值,#{}括号中可以是value或其他属性。

    • ${}表示拼接字符串,通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。

  2. parameterType和resultType

    • parameterType:指定输入参数类型,mybatis通过OGNL表达式从对象中获取参数值拼接在SQL中。

    • resultType:指定输出结果类型,mybatis将SQL查询结果的一行记录数据映射为resultType指定类型的对象。

  3. selectOne和selectList

    • selectOne:查询一条记录,使用其查询多条记录会抛出异常

    • selectList:查询一条或者多条记录

7、添加用户

  1. 在SQLMapConfig.xml中添加:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--如果从传入的是pojo类型,name#{}中对应的属.属性-->
    <!--如果要返回数据库自增组件,可以使用select last_insert_id()函数-->
    <insert id="insertUser" parameterType="cn.itheima.pojo.User">
    <!--执行数据库函数,返回最近增加的自增主键id
    keyProperty:将返回的主键放入传入的参数的ID中保存
    order:当前函数相对于insert语句的执行顺序
    resultType:keyProperty中的数据的类型
    -->
    <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
    select last_insert_id()
    </selectKey>
    insert into user (username, birthday, sex, address) values (#{username}, #{birthday}, #{sex}, #{address})
    </insert>
  2. 测试程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //omit the before
    @Test
    public void testInsertUser() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //创建插入数据库的用户对象
    User user = new User();
    user.setUsername("aaa");
    user.setBirthday(new Date());
    user.setSex("1");
    user.setAddress("北京");
    System.out.println(user);
    //执行SQL语句插入对象
    sqlSession.insert("test.insertUser", user);
    //提交事务,mybatis会自动开启事务,需要手动提交事务
    sqlSession.commit();
    System.out.println(user);
    }
  3. uuid实现主键:AFTER改为BEFORE,则在插入之前生成UUID并保存到数据库中

    1
    2
    3
    4
    5
    6
    7
    8
    <insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
    <selectKey resultType="java.lang.String" order="BEFORE"
    keyProperty="id">
    select uuid()
    </selectKey>
    insert into user(id,username,birthday,sex,address)
    values(#{id},#{username},#{birthday},#{sex},#{address})
    </insert>

8、删除用户

  1. 在SQLMapConfig.xml中添加:

    1
    2
    3
    <delete id="deleteUserById" parameterType="java.lang.Integer">
    delete from user where id=#{id}
    </delete>
  2. 测试程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //omit the before
    @Test
    public void testDeleteUserById() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //删除
    sqlSession.delete("test.deleteUserById", 28);
    //提交
    sqlSession.commit();
    }

9、修改用户

  1. 在SQLMapConfig.xml中添加:

    1
    2
    3
    <update id="updateUserById" parameterType="cn.itheima.pojo.User">
    update user set username = #{username} where id = #{id}
    </update>
  2. 测试程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    public void testUpdateUserById() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    User user = new User();
    user.setId(29);
    user.setUsername("王麻子");
    sqlSession.update("test.updateUserById", user);
    sqlSession.commit();
    }

10、mybatis解决jdbc编程的问题

  1. 数据库连接创建,释放频繁造成系统资源浪费从而影响系统性能,可以使用数据库连接池技术来解决这个问题。

    解决:在SQLMapConfig.xml中配置数据连接池,使用连接池管理数据库连接

  2. SQL语句在代码中硬编码,不便于维护,实际应用中修改SQL语句需要修改java代码,不便于修改。

    解决:将SQL语句配置在XXmapper.xml文件中与java程序分离

  3. 使用preparedStatement语句向占位符传参存在硬编码,实际上SQL语句的where条件不固定,修改困难

    解决:mybatis自动将java对象映射至SQL语句,通过statement中的parameterType定义输入参数的类型

  4. 对结果集存在硬编码(查询列名),SQL变化导致解析代码变化,系统不易维护,将数据库记录封装成pojo对象解析比较方便。

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

11、mybatis和hibernate的区别

  1. Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。

  2. Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。

  3. Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。

三、DAO的开发方法

使用mybatis开发DAO,有两种方法,原始DAO方法和Mapper接口开发方法

1、需求

  • 根据用户id查询一个用户信息

  • 根据用户名称模糊查询用户信息列表

  • 添加用户信息

2、SqlSession的使用范围

SqlSession中封装了对数据库的操作,如:查询、插入、更新、删除等。通过SqlSessionFactory创建SqlSession,而SqlSessionFactory是通过SqlSessionFactoryBuilder进行创建。

  1. SqlSessionFactoryBuilder:用于创建SqlSessionFacoty,SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory生产,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。

  2. SqlSessionFactory:SQLSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个运行期间,一旦创建之后可以重复使用,通常以单例模式管理SqlSessionFactory。

  3. SqlSession: SqlSession是一个面向用户的接口,sqlSession中定义了数据库操作方法。每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。打开一个 SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。

3、原始DAO开发方式(需要编写接口和实现类)

  • 映射文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?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">
    <!--命名空间的作用为SQL隔离-->
    <mapper namespace="test">
    <!--根据id获取用户信息-->
    <select id="findUserById" parameterType="java.lang.Integer" resultType="cn.itheima.pojo.User">
    select * from user where id = #{id}
    </select>
    <select id="findUserByUserName" parameterType="java.lang.String" resultType="cn.itheima.pojo.User">
    select * from user where username like '%${value}%'
    </select>
    <insert id="insertUser" parameterType="cn.itheima.pojo.User">
    <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
    select last_insert_id()
    </selectKey>
    insert into user (username, birthday, sex, address) values (#{username}, #{birthday}, #{sex}, #{address})
    </insert>
    </mapper>
  • Dao接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package cn.itheima.dao;
    import cn.itheima.pojo.User;
    import java.util.List;
    public interface UserDao {
    public User findUserById(Integer id);
    public List<User> findUserByUserName(String username);
    }
  • 实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package cn.itheima.dao;
import cn.itheima.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import java.util.List;
public class UserDaoImpl implements UserDao{
private SqlSessionFactory sqlSessionFactory;
//通过构造方法注入
public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public User findUserById(Integer id) {
//sqlSession是县城不安全的,所以他是最佳使用范围在方法体内
SqlSession openSession = sqlSessionFactory.openSession();
User user = openSession.selectOne("test.findUserById", id);
return user;
}
@Override
public List<User> findUserByUserName(String username) {
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> users = sqlSession.selectList("test.findUserByUserName", username);
return users;
}
}
  • 测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    package test.cn.itheima.dao;
    import cn.itheima.dao.UserDao;
    import cn.itheima.dao.UserDaoImpl;
    import cn.itheima.pojo.User;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.Test;
    import org.junit.Before;
    import org.junit.After;
    import java.io.InputStream;
    import java.util.List;
    /**
    * UserDaoImpl Tester.
    *
    * @author <Authors name>
    * @since <pre>一月 24, 2018</pre>
    * @version 1.0
    */
    public class UserDaoImplTest {
    private SqlSessionFactory sqlSessionFactory;
    private UserDao userDao;
    @Before
    public void before() throws Exception {
    String resource = "SqlMapConfig.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    userDao = new UserDaoImpl(sqlSessionFactory);
    }
    @After
    public void after() throws Exception {
    }
    /**
    *
    * Method: findUserById(Integer id)
    *
    */
    @Test
    public void testFindUserById() throws Exception {
    User user = userDao.findUserById(1);
    System.out.println(user);
    }
    /**
    *
    * Method: findUserByUserName(String username)
    *
    */
    @Test
    public void testFindUserByUserName() throws Exception {
    List<User> users = userDao.findUserByUserName("王");
    for (User user : users) {
    System.out.println(user);
    }
    }
    }

4、动态代理方法

1、开发规范

Mapper接口开发方法只需要程序员编写Mapper接口,由mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上班DAO接口实现类方法。Mapper接口需要遵守以下规范:

  1. Mapper.xml文件中的namespace与mapper接口的类路径相同

  2. Mapper接口方法名和Mapper.xml中定义的每个statement的id相同

  3. Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同

  4. Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType类型相同。

2、Mapper.xml(映射文件)

定义mapper映射文件UserMapper.xml,需要修改namespace的值为UserMapper接口路径,经UserMapper.xml放在mapper路径下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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.mybatis.mapper.UserMapper">
<!-- 根据id获取用户信息 -->
<select id="findUserById" parameterType="int" resultType="cn.itcast.mybatis.po.User">
select * from user where id = #{id}
</select>
<!-- 自定义条件查询用户列表 -->
<select id="findUserByUsername" parameterType="java.lang.String"
resultType="cn.itcast.mybatis.po.User">
select * from user where username like '%${value}%'
</select>
<!-- 添加用户 -->
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
select LAST_INSERT_ID()
</selectKey>
insert into user(username,birthday,sex,address)
values(#{username},#{birthday},#{sex},#{address})
</insert>
</mapper>

3、Mapper.java(接口文件)

1
2
3
4
5
6
7
8
Public interface UserMapper {
//根据用户id查询用户信息
public User findUserById(int id) throws Exception;
//查询用户列表
public List<User> findUserByUsername(String username) throws Exception;
//添加用户信息
public void insertUser(User user)throws Exception;
}

接口定义有如下特点:

  1. Mapper接口名和Mapper.xml中定义的statement的id相同

  2. Mapper接口方法的输入参数类型和mapper.xml中定义的statement的parameterType的类型相同。

  3. Mapper接口方法的输出参数类型和mapper.xml中定义的statement的resultType的类型相同

4、在SQLMapConfig.xml中配置映射文件

1
2
3
4
<!-- 加载映射文件 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>

5、测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
Public class UserMapperTest extends TestCase {
private SqlSessionFactory sqlSessionFactory;
protected void setUp() throws Exception {
//mybatis配置文件
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//使用SqlSessionFactoryBuilder创建sessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
Public void testFindUserById() throws Exception {
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获取mapper接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用代理对象方法
User user = userMapper.findUserById(1);
System.out.println(user);
//关闭session
session.close();
}
@Test
public void testFindUserByUsername() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> list = userMapper.findUserByUsername("张");
System.out.println(list.size());
}
Public void testInsertUser() throws Exception {
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获取mapper接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//要添加的数据
User user = new User();
user.setUsername("张三");
user.setBirthday(new Date());
user.setSex("1");
user.setAddress("北京市");
//通过mapper接口添加用户
userMapper.insertUser(user);
//提交
session.commit();
//关闭session
session.close();
}
}

6、小结

  1. selectOne和selectList:动态代理对象调用sqlSession.selectOne()和sqlSession.selectList()是根据mapper接口的返回值决定,如果返回list则调用selectList方法,如果返回单个对象则调用selectOne方法。

  2. namespace:mybatis官方推荐使用mapper代理方法开发mapper接口,程序员不用编写mapper接口实现类,使用mapper代理方法时,输入参数可以使用pojo包装对象或map对象,保证dao的通用性。

四、SqlMapConfig.xml文件说明

1、配置内容:

  • properties(属性)

  • settings(全局配置参数)

  • typeAliases(类型别名)

  • typeHandlers(类型处理器)

  • objectFactory(对象工厂)

  • plugins(插件)

  • environments(环境集合属性对象)

  • environment(环境子属性对象)

  • transactionManager(事务管理)

  • dataSource(数据源)

  • mappers(映射器)

2、properties(属性)

SqlMapConfig.xml可以引用java属性文件中的配置信息,创建db.properties保存数据库属性:

1
2
3
4
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8
jdbc.username=root
jdbc.password=linux

修改SqlMapConfig.xml中引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<configuration>
<!--加载db.properties中的参数-->
<properties resource="db.properties"/>
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理-->
<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>
</environment>
</environments>
</configuration>

其属性读取顺序为:

  1. 在properties元素体内定义的属性会首先被读取
  2. 然后会读取properties元素中的resource或url加载的属性,它会覆盖已经读取的同名属性。

3、typeAliases(类型别名)

1、mybatis支持别名:

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
map Map

2、自定义别名:在SqlMapConfig.xml中配置:

1
2
3
4
5
6
7
<!--别名-->
<typeAliases>
<!--定义单个pojo类别名 type 类的全路径名, alias别名-->
<typeAlias type="cn.itheima.pojo.User" alias="user"/>
<!--使用包扫描的方式数量定义别名,定以后别名等于类名,不区分大小写,建议按照Java命名规则来-->
<package name="cn.itheima.pojo"/>
</typeAliases>

4、Mappers(映射器)

  1. : 使用相对于类路径的资源,如:

  2. :使用mapper接口类路径,如:

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

  1. :注册指定包下的所有mapper接口,如:

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

5、传递pojo包装类内容(输入类型的一种)

开发中通过pojo传递查询条件,查询条件是综合的查询条件,不仅包括用户查询条件,还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。Pojo类中包含pojo。

需求:根据用户名查询用户信息,查询条件放到QueryVo的user属性中。

  1. QueryVo:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package cn.itheima.pojo;
    import java.util.List;
    public class QueryVo {
    private User user;
    private List<Integer> ids;
    public List<Integer> getIds() {
    return ids;
    }
    public void setIds(List<Integer> ids) {
    this.ids = ids;
    }
    public User getUser() {
    return user;
    }
    public void setUser(User user) {
    this.user = user;
    }
    }
  2. sql语句

    select * from user where username like '%王%'

  3. Mapper文件

    1
    2
    3
    <select id="findUserByVo" parameterType="cn.itheima.pojo.QueryVo" resultType="cn.itheima.pojo.User">
    select * from user where username LIKE '%${user.username}%' AND sex=#{user.sex}
    </select>
  4. 接口

    1
    public List<User> findUserByVo(QueryVo queryVo);
  5. 测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Test
    public void findUserByVo() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    QueryVo queryVo = new QueryVo();
    User user = new User();
    user.setUsername("王");
    user.setSex("1");
    queryVo.setUser(user);
    List<User> users = userMapper.findUserByVo(queryVo);
    System.out.println(users);
    }

6、resultType(输出类型)

1、输出简单类型

1
2
3
<select id="findUserCount" resultType="java.lang.Long">
select count(*) from user
</select>
1
public Long findUserCount();
1
2
3
4
5
6
@Test
public void testFindUserCount() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
System.out.println(userMapper.findUserCount());
}

2、输出pojo对象或者pojo对象列表

1
2
3
4
5
6
7
<select id="findUserById" parameterType="java.lang.Integer" resultType="user">
select * from user where id = #{id}
</select>
<select id="findUserByUserName" parameterType="java.lang.String" resultType="User">
select * from user where username like '%${value}%'
</select>
1
2
3
public User findUserById(Integer id);
// 动态代理模式中,如果返回结果集WieList,mybatis会在生成实现类的时候自动调用selectList方法
public List<User> findUserByUserName(String username);

7、resultMap

resultType可以指定pojo将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。如果sql查询字段名和pojo的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系 ,resultMap实质上还需要将查询结果映射到pojo对象中。resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。

  1. Mapper.xml的定义:使用resultMap来指定输出内容映射

    1
    2
    3
    4
    5
    <select id="findOrdersAndUser2" resultMap="orderAndUserResultMap">
    select a.*, b.id uid, username, birthday, sex, address
    from orders a, user b
    where a.user_id = b.id
    </select>
  2. 定义resultMap

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <!--一对一,手动映射-->
    <!--id resultMap的唯一标识,type为将查询出的数据放入这个指定的对象
    手动映射需要制定数据库中表的字段名与java中Pojo类的属性名称的对应关系
    -->
    <resultMap id="orderAndUserResultMap" type="cn.itheima.pojo.Orders">
    <!--id标签制定主键字段对应关系, column为数据库中的字段名,property为pojo类的属性名-->
    <id column="id" property="id"/>
    <!--result标签制定非主键列的对应关系-->
    <result column="user_id" property="userId"/>
    <result column="number" property="number"/>
    <result column="createtime" property="createtime"/>
    <result column="note" property="note"/>
    <!--user对象的映射关系的指定
    property:指定将数据放入Orders中的user属性中,
    type为user的类型
    -->
    <association property="user" javaType="cn.itheima.pojo.User">
    <id column="uid" property="id"/>
    <result column="username" property="username"/>
    <result column="birthday" property="birthday"/>
    <result column="sex" property="sex"/>
    <result column="address" property="address"/>
    </association>
    </resultMap>
  3. Mapper接口定义

    1
    public List<Orders> findOrdersAndUser2();

五、动态sql

通过mybatis提供的各种标签方法实现动态拼接SQL。

1、if

1
2
3
4
5
6
7
8
9
10
<select id="findUserByUserNameAndSex" parameterType="cn.itheima.pojo.User" resultType="cn.itheima.pojo.User">
select * from user
where 1=1
<if test="id!=null">
and id=#{id}
</if>
<if test="username!=null and username!=''">
and username like '%${username}%'
</if>
</select>

空字符串校验

2、where

1
2
3
4
5
6
7
8
9
10
11
<select id="findUserByUserNameAndSex" parameterType="cn.itheima.pojo.User" resultType="cn.itheima.pojo.User">
select * from user
<where>
<if test="id!=null and id!=''">
and id=#{id}
</if>
<if test="username!=null and username!=''">
and username like '%${username}%'
</if>
</where>
</select>

会自动向SQL语句中添加where关键字,会去掉第一个条件的and关键字

3、foreach

向SQL传递数组或List,mybatis使用foreach解析,需求在传入多个id查询用户信息时需要。

  • 需要在Vo中定义list属性ids存储多个用户id,并添加getter、setter方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private List<Integer> ids;
    public List<Integer> getIds() {
    return ids;
    }
    public void setIds(List<Integer> ids) {
    this.ids = ids;
    }
  • mapper.xml中的编写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <select id="findUserByIds" parameterType="cn.itheima.pojo.QueryVo" resultType="user">
    select * from user
    <where>
    <if test="ids != null and ids.size > 0">
    <!--foreach:循环传入的集合参数
    collection:传入的集合的变量名称
    item:每次循环将循环出的数据放入这个变量中
    open:循环开始拼接的字符串
    close:循环结束拼接的字符串
    seperator:循环中拼接的字符串
    -->
    <foreach collection="ids" item="id" open="and id in (" close=")" separator=",">
    #{id}
    </foreach>
    </if>
    </where>
    </select>
  • 测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Test
    public void testFindUserByIds() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    QueryVo vo = new QueryVo();
    List<Integer> ids = new ArrayList<>();
    ids.add(1);
    ids.add(10);
    ids.add(16);
    ids.add(28);
    ids.add(22);
    vo.setIds(ids);
    List<User> users = userMapper.findUserByIds(vo);
    for (User user : users) {
    System.out.println("===============" + user + "===============");
    }
    }

4、sql片段

sql中可将重复的SQL提取出来,使用时用include引用即可,从而达到SQL重用的目的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--封装SQL条件,封装后可以重用,id为SQL的唯一标示-->
<sql id="user_Where">
<!--会自动向SQL语句中添加where关键字,
会去掉第一个条件的and关键字-->
<where>
<if test="username != null and username != ''">
and username like '%${username}%'
</if>
<if test="sex != null and sex != ''">
and sex=#{sex}
</if>
</where>
</sql>
......
<select id="findUserByUserNameAndSex" parameterType="cn.itheima.pojo.User" resultType="cn.itheima.pojo.User">
select * from user
<!--调用SQL条件-->
<include refid="user_Where"/>
</select>

六、关联查询

1、商品订单数据模型

Alt text

2、一对一关联

需求:查询所有订单信息,关联查询下单用户信息。由于一个订单信息只能由一个用户下单,所以从查询订单信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的订单为一对多查询。

方法一:使用resultType,定义订单信息po类,此po类中包括了订单信息和用户信息。

  1. SQL语句

    1
    2
    3
    select a.*, b.id uid, username, birthday, sex, address
    from orders a, user b
    where a.user_id = b.id
  2. 定义po类:po类应该包含sql查询的所有字段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package cn.itheima.pojo;
    import java.util.Date;
    public class CustomOrders extends Orders{
    private int uid;
    private String username;// 用户姓名
    private String sex;// 性别
    private Date birthday;// 生日
    private String address;// 地址
    //omit...

    OrdersCustomer类继承Orders类后包括了Orders类的所有字段,只需要定义用户的信息字段即可。

  3. Mapper.xml中

    1
    2
    3
    4
    5
    6
    <!--一对一,自动映射 -->
    <select id="findOrdersAndUser1" resultType="cn.itheima.pojo.CustomOrders">
    select a.*, b.id uid, username, birthday, sex, address
    from orders a, user b
    where a.user_id = b.id
    </select>
  4. Mapper接口

    1
    public List<CustomOrders> findOrdersAndUser1();
  5. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Test
    public void testFindOrdersAndUser1() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<CustomOrders> customOrders = userMapper.findOrdersAndUser1();
    for (CustomOrders customOrders1 : customOrders) {
    System.out.println("===============" + customOrders1 + "===============");
    }
    }
  6. 小结:
    定义专门的po类作为输出类型,其中定义了SQL查询结果集的所有字段,此方法比较简单,企业中使用普遍。

方法二:使用resultMap,定义专门的resultMap用于映射一对一查询

  1. SQL语句

    1
    2
    3
    select a.*, b.id uid, username, birthday, sex, address
    from orders a, user b
    where a.user_id = b.id
  2. 定义po类:在Orders类中加入User属性,User属性中用于存储关联查询的用户信息,因为订单关联查询是一对一关系,所以这里使用单个User对象存储关联查询的用户信息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package cn.itheima.pojo;
    import java.util.Date;
    import java.util.List;
    public class Orders {
    private Integer id;
    private Integer userId;
    private String number;
    private Date createtime;
    private String note;
    private User user;
    //omit...
    }
  3. Mapper.xml中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <resultMap id="orderAndUserResultMap" type="cn.itheima.pojo.Orders">
    <!--id标签制定主键字段对应关系, column为数据库中的字段名,property为pojo类的属性名-->
    <id column="id" property="id"/>
    <!--result标签制定非主键列的对应关系-->
    <result column="user_id" property="userId"/>
    <result column="number" property="number"/>
    <result column="createtime" property="createtime"/>
    <result column="note" property="note"/>
    <!--user对象的映射关系的指定
    property:指定将数据放入Orders中的user属性中,
    type为user的类型
    -->
    <association property="user" javaType="cn.itheima.pojo.User">
    <id column="uid" property="id"/>
    <result column="username" property="username"/>
    <result column="birthday" property="birthday"/>
    <result column="sex" property="sex"/>
    <result column="address" property="address"/>
    </association>
    </resultMap>
    <select id="findOrdersAndUser2" resultMap="orderAndUserResultMap">
    select a.*, b.id uid, username, birthday, sex, address
    from orders a, user b
    where a.user_id = b.id
    </select>
    • association:表示进行关联查询单条记录
    • property:表示关联查询的结果存储在cn.itcast.mybatis.po.Orders的user属性中
    • javaType:表示关联查询的结果类型
    • :查询结果的user_id列对应关联对象的id属性,这里是表示user_id是关联查询对象的唯一标识。
    • :查询结果的username列对应关联对象的username属性
  1. Mapper接口

    1
    public List<Orders> findOrdersAndUser2();
  2. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void testFindOrdersAndUser2() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<Orders> orders = userMapper.findOrdersAndUser2();
    System.out.println(orders);
    }
  3. 小结:使用association完成关联查询,将关联查询信息映射到pojo对象中。

3、一对多查询

需求:查询所有用户信息及用户关联的订单信息,用户信息和订单信息为一对多关系。使用resultMap实现:

  1. SQL语句

    1
    2
    select a.*, b.id oid, user_id, number, createtime
    from user a, orders b where a.id = b.user_id
  2. 定义po类:在User类中加入List orders属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package cn.itheima.pojo;
    import java.util.Date;
    import java.util.List;
    public class User {
    private int id;
    private String username;// 用户姓名
    private String sex;// 性别
    private Date birthday;// 生日
    private String address;// 地址
    private List<Orders> ordersList;
    //omit....
    }
  3. Mapper.xml中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <resultMap id="userAndOrdersResultMap" type="cn.itheima.pojo.User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="sex" property="sex"/>
    <result column="birthday" property="birthday"/>
    <result column="address" property="address"/>
    <!--订单集合-->
    <!--collection指定对应的集合关系映射
    property:将数据放入指定的User对象的OrdersList集合属性中
    ofType: 指定OrderList中的泛型类型
    -->
    <collection property="ordersList" ofType="cn.itheima.pojo.Orders">
    <id column="oid" property="id"/>
    <result column="user_id" property="userId"/>
    <result column="number" property="number"/>
    <result column="createtime" property="createtime"/>
    </collection>
    </resultMap>
    <select id="findUserAndOrders" resultMap="userAndOrdersResultMap">
    select a.*, b.id oid, user_id, number, createtime
    from user a, orders b where a.id = b.user_id
    </select>
    • collections:定义了用户关联的订单信息,表示关联查询结果集
    • property:关联查询的结果集存储在User对象上的那个属性
    • ofType:指定关联查询的结果集中的对象类型及List中的对象类型。此处可以使用别名也可以使用全限定名
    • :查询结果的oid列对应关联对象的id属性,这里是表示oid是关联查询对象的唯一标识。
    • :查询结果的userId列对应关联对象的user_id属性
  1. Mapper接口

    1
    public List<User> findUserAndOrders();
  2. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void testFindUserAndOrders() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> users = userMapper.findUserAndOrders();
    System.out.println(users);
    }

七、Spring整合mybatis

1、整合思路

  1. SqlSessionFactory对象应该放入Spring容器中作为单例存在。

  2. 传统dao的开发方式中,应该从Spring容器中获取SqlSession对象。

  3. Mapper代理形式中,应该从Spring容器中直接获得mapper对象。

  4. 数据库的链接记忆数据库连接池事务都交给spring容器来完成。

2、整合需要的jar包

  1. Spring的jar包

  2. mybatis的jar包

  3. spring+mybatis的整合包

  4. MySQL数据库驱动包

  5. 数据库连接池的jar包

    Alt text

3、整合的步骤

  1. 创建一个java工程

  2. 导入jar包

  3. mybatis配置文件SQLMapConfig.xml

  4. 编写Spring配置文件,包括数据库连接及连接池、事务管理、SqlSessionFactory对象,配置到Spring容器中、,mapper代理对象或者dao实现类配置到Spring容器中。

  5. 编写dao或者mapper文件

  6. 测试

1、SqlMapConfig.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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>
<!--别名-->
<typeAliases>
<!--定义单个pojo类别名 type 类的全路径名, alias别名-->
<typeAlias type="cn.itheima.pojo.User" alias="user"/>
<!--使用包扫描的方式数量定义别名,定以后别名等于类名,不区分大小写,建议按照Java命名规则来-->
<package name="cn.itheima.pojo"/>
</typeAliases>
<mappers>
<mapper resource="User.xml"/>
<!--使用class属性引入接口的全路径名称,使用规则
1、接口名称和映射文件名称除了扩展名之外完全相同,
2、接口和映射文件要放在同一个目录下
-->
<!--<mapper class="UserMapper"/>-->
<!--使用包扫描的方式批量引入Mapper接口
1、接口名称和映射文件名称除了扩展名之外完全相同,
2、接口和映射文件要放在同一个目录下-->
<package name="cn.itheima.mapper"/>
</mappers>
</configuration>

2、applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.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}" />
<property name="maxActive" value="10" />
<property name="maxIdle" value="5" />
</bean>
<!-- mapper配置 -->
<!-- 让spring管理sqlsessionfactory 使用mybatis和spring整合包中的 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据库连接池 -->
<property name="dataSource" ref="dataSource" />
<!-- 加载mybatis的全局配置文件 -->
<property name="configLocation" value="classpath:SqlMapConfig.xml" />
</bean>
<!--配置原生DAO实现-->
<!--<bean id="userDao" class="cn.itheima.dao.UserDaoImpl">-->
<!--<property name="sqlSessionFactory" ref="sqlSessionFactory"/>-->
<!--</bean>-->
<!--Mapper接口代理的实现-->
<!--<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">-->
<!--&lt;!&ndash;接口路径名&ndash;&gt;-->
<!--<property name="mapperInterface" value="UserMapper"/>-->
<!--<property name="sqlSessionFactory" ref="sqlSessionFactory"/>-->
<!--</bean>-->
<!--使用包扫描的方式引入Mapper-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定要扫描的包的全路径名称,如果有多个包,用英文逗号分隔-->
<!--扫描后引用的时候使用类名首字母小写-->
<property name="basePackage" value="cn.itheima.mapper"/>
</bean>
</beans>

3、db.properties

1
2
3
4
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8
jdbc.username=root
jdbc.password=linux

4、DAO的开发

三种dao的实现方式:

  1. 传统dao的开发方式

  2. 使用mapper代理形式开发方式

  3. 使用扫描包配置mapper代理

1、使用传统dao的开发方式

接口+实现类来完成。dao实现类需要继承SqlSessionDaoSupport.

  1. 实现类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package cn.itheima.dao;
    import cn.itheima.pojo.User;
    import org.apache.ibatis.session.SqlSession;
    import org.mybatis.spring.support.SqlSessionDaoSupport;
    import java.util.List;
    public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao{
    @Override
    public User findUserById(Integer id) {
    //sqlSession是线程不安全的,所以他是最佳使用范围在方法体内
    SqlSession openSession = this.getSqlSession();
    User user = openSession.selectOne("test.findUserById", id);
    return user;
    }
    @Override
    public List<User> findUserByUserName(String username) {
    SqlSession sqlSession = this.getSqlSession();
    List<User> users = sqlSession.selectList("test.findUserByUserName", username);
    return users;
    }
    }
  2. 配置DAO:

    1
    2
    3
    4
    <!--配置原生DAO实现-->
    <bean id="userDao" class="cn.itheima.dao.UserDaoImpl">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
  3. 测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package cn.itheima.test;
    import cn.itheima.dao.UserDao;
    import cn.itheima.pojo.User;
    import org.junit.Before;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    public class UserDaoTest {
    private ApplicationContext applicationContext;
    @Before
    public void setUp() throws Exception {
    String configLocation = "applicationContext.xml";
    applicationContext = new ClassPathXmlApplicationContext(configLocation);
    }
    @Test
    public void testFindUserById() throws Exception {
    //获取DAO对象,getBean中的字符串为配置文件中声明的
    UserDao userDao = (UserDao) applicationContext.getBean("userDao");
    User user = userDao.findUserById(1);
    System.out.println(user);
    }
    }

2、Mapper代理形式开发dao

按照代理的要求,编写映射文件和接口,放置在同一个路径下。

  1. 配置

    1
    2
    3
    4
    5
    6
    <!--Mapper接口代理的实现-->
    <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    &lt;!&ndash;接口路径名&ndash;&gt;
    <property name="mapperInterface" value="UserMapper"/>
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
  2. 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package cn.itheima.test;
import cn.itheima.mapper.UserMapper;
import cn.itheima.pojo.User;
import cn.itheima.pojo.UserExample;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class UserMapperTest {
private ApplicationContext applicationContext;
@Before
public void setUp() throws Exception {
String configLocation = "applicationContext.xml";
applicationContext = new ClassPathXmlApplicationContext(configLocation);
}
// @Test
// public void testFindUserById() throws Exception {
// //获取DAO对象,getBean中的字符串为配置文件中声明的
// UserMapper userMapper = (UserMapper) applicationContext.getBean("userMapper");
//
// User user = userMapper.findUserById(1);
//
// System.out.println(user);
// }
@Test
public void testFindUserById() throws Exception {
//获取DAO对象,getBean中的字符串为配置文件中声明的
UserMapper userMapper = (UserMapper) applicationContext.getBean("userMapper");
User user = userMapper.selectByPrimaryKey(1);
System.out.println(user);
}
@Test
public void testFindUserAndSex() throws Exception{
//获取DAO对象,getBean中的字符串为配置文件中声明的
UserMapper userMapper = (UserMapper) applicationContext.getBean("userMapper");
//创建UserExample对象
UserExample userExample = new UserExample();
//创建查询条件封装对象
UserExample.Criteria criteria = userExample.createCriteria();
//输入条件
criteria.andUsernameLike("%王%");
criteria.andSexEqualTo("1");
List<User> user = userMapper.selectByExample(userExample);
System.out.println(user);
}
}

3、使用包扫描的方式配置mapper

1
2
3
4
5
6
<!--使用包扫描的方式引入Mapper-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定要扫描的包的全路径名称,如果有多个包,用英文逗号分隔-->
<!--扫描后引用的时候使用类名首字母小写-->
<property name="basePackage" value="cn.itheima.mapper"/>
</bean>

每个mapper代理对象的id就是类名首字母小写

八、mybatis逆向工程

使用官方网站的mapper自动生成工具mybatis-generator-core-1.3.2来生成po类和mapper映射文件。

作用:mybatis官方提供逆向工程,可以使用它通过数据库中的表来自动生成Mapper接口和映射文件(单表增删改查)和Po类.

需要导入的jar包包括:

Alt text

1、mapper生成配置文件

在generatorConfig.xml中配置mapper生成的详细信息,需要注意:

  1. 添加要生成的数据库表

  2. po文件所在的包路径

  3. mapper文件所在的包路径

配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?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://localhost:3306/springmvc" userId="root"
password="linux">
</jdbcConnection>
<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver"
connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg"
userId="yycg"
password="yycg">
</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.itheima.pojo"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="cn.itheima.dao"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="cn.itheima.dao"
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>
<!-- <table schema="" tableName="sys_user"></table>
<table schema="" tableName="sys_role"></table>
<table schema="" tableName="sys_permission"></table>
<table schema="" tableName="sys_user_role"></table>
<table schema="" tableName="sys_role_permission"></table> -->
<!-- 有些表的字段需要指定java类型
<table schema="" tableName="">
<columnOverride column="" javaType="" />
</table> -->
</context>
</generatorConfiguration>

2、使用java类生成mapper文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class StartServer {
public void generator() throws Exception{
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("generator.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);
}
public static void main(String[] args) throws Exception {
try {
StartServer startServer = new StartServer();
startServer.generator();
} catch (Exception e) {
e.printStackTrace();
}
}
}

3、将生成的mapper文件拷贝到指定目录

注意:mapper xml文件和mapper.java文件在一个目录内且文件名相同。

4、测试mapper接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package cn.itheima.dao;
import cn.itheima.pojo.Items;
import cn.itheima.pojo.ItemsExample;
import java.util.List;
import org.apache.ibatis.annotations.Param;
public interface ItemsMapper {
int countByExample(ItemsExample example);
int deleteByExample(ItemsExample example);
int deleteByPrimaryKey(Integer id);
int insert(Items record);
int insertSelective(Items record);
List<Items> selectByExampleWithBLOBs(ItemsExample example);
List<Items> selectByExample(ItemsExample example);
Items selectByPrimaryKey(Integer id);
int updateByExampleSelective(@Param("record") Items record, @Param("example") ItemsExample example);
int updateByExampleWithBLOBs(@Param("record") Items record, @Param("example") ItemsExample example);
int updateByExample(@Param("record") Items record, @Param("example") ItemsExample example);
int updateByPrimaryKeySelective(Items record);
int updateByPrimaryKeyWithBLOBs(Items record);
int updateByPrimaryKey(Items record);
}

5、注意事项

  1. Mapper文件内容时追加不是覆盖。所以XXXMapper.xml文件已经存在时,如果进行重新生成则mapper.xml文件内容不被覆盖而是进行内容追加,结果导致mybatis解析失败。
    解决方法:删除原来已经生成的mapper xml文件再进行生成。Mybatis自动生成的po及mapper.java文件不是内容而是直接覆盖没有此问题。

  2. Table Schema问题:

下边是关于针对oracle数据库表生成代码的schema问题:

Schma即数据库模式,oracle中一个用户对应一个schema,可以理解为用户就是schema。
当Oralce数据库存在多个schema可以访问相同的表名时,使用mybatis生成该表的mapper.xml将会出现mapper.xml内容重复的问题,结果导致mybatis解析错误。
解决方法:在table中填写schema,如下:


XXXX即为一个schema的名称,生成后将mapper.xml的schema前缀批量去掉,如果不去掉当oracle用户变更了sql语句将查询失败。
快捷操作方式:mapper.xml文件中批量替换:“from XXXX.”为空

Oracle查询对象的schema可从dba_objects中查询,如下:
select * from dba_objects

九、总结

  1. mybatis是一个持久层框架, 作用是跟数据库交互完成增删改查

  2. 原生Dao实现(需要接口和实现类)

  3. 动态代理方式(只需要接口)
    mapper接口代理实现编写规则:

    1. 映射文件中namespace要等于接口的全路径名称
    2. 映射文件中sql语句id要等于接口的方法名称
    3. 映射文件中传入参数类型要等于接口方法的传入参数类型
    4. 映射文件中返回结果集类型要等于接口方法的返回值类型
  4. #{}占位符:占位
    如果传入的是基本类型,那么#{}中的变量名称可以随意写
    如果传入的参数是pojo类型,那么#{}中的变量名称必须是pojo中的属性.属性.属性…

  5. ${}拼接符:字符串原样拼接
    如果传入的是基本类型,那么${}中的变量名必须是value
    如果传入的参数是pojo类型,那么${}中的变量名称必须是pojo中的属性.属性.属性…
    注意:使用拼接符有可能造成sql注入,在页面输入的时候可以加入校验,不可输入sql关键字,不可输入空格

  6. 映射文件:

    1. 传入参数类型通过parameterType属性指定
    2. 返回结果集类型通过resultType属性指定
  7. hibernate和mybatis区别:

    • hibernate:它是一个标准的orm框架,比较重量级,学习成本高.
      优点:高度封装,使用起来不用写sql,开发的时候,会减低开发周期.
      缺点:sql语句无法优化
      应用场景:oa(办公自动化系统), erp(企业的流程系统)等,还有一些政府项目,
      总的来说,在用于量不大,并发量小的时候使用.
      
    • mybatis:它不是一个orm框架, 它是对jdbc的轻量级封装, 学习成本低,比较简单
      有点:学习成本低, sql语句可以优化, 执行效率高,速度快
      缺点:编码量较大,会拖慢开发周期
      应用场景: 互联网项目,比如电商,P2p等
      总的来说是用户量较大,并发高的项目.
      
  8. 输入映射(就是映射文件中可以传入哪些参数类型)

    • 1)基本类型
      • 2)pojo类型
      • 3)Vo类型
  9. 输出映射(返回的结果集可以有哪些类型)

    • 1)基本类型
    • 2)pojo类型
    • 3)List类型
  10. 动态sql:动态的拼接sql语句,因为sql中where条件有可能多也有可能少

    • 1)where:可以自动添加where关键字,还可以去掉第一个条件的and关键字
    • 2)if:判断传入的参数是否为空
    • 3)foreach:循环遍历传入的集合参数
    • 4)sql:封装查询条件,以达到重用的目的
  11. 对单个对象的映射关系:

    • 1)自动关联(偷懒的办法):可以自定义一个大而全的pojo类,然后自动映射其实是根据数据库总的字段名称和pojo中的属性名称对应.
    • 2)手动关联: 需要指定数据库中表的字段名称和java的pojo类中的属性名称的对应关系.使用association标签
  12. 对集合对象的映射关系
    只能使用手动映射:指定表中字段名称和pojo中属性名称的对应关系使用collection标签

  13. spring和mybatis整合
    整合后会话工厂都归spring管理

    *     1)原生Dao实现:
             需要在spring配置文件中指定dao实现类
             dao实现类需要继承SqlSessionDaoSupport超类
             在dao实现类中不要手动关闭会话,不要自己提交事务.
    *     2)Mapper接口代理实现:
             在spring配置文件中可以使用包扫描的方式,一次性的将所有mapper加载
    
  14. 逆向工程:自动生成Pojo类,还可以自动生成Mapper接口和映射文件

    注意:生成的方式是追加而不是覆盖,所以不可以重复生成,重复生成的文件有问题.
        如果想重复生成将原来生成的文件删除
    

概念区分:

  1. pojo:不按mvc分层,只是java bean有一些属性,还有get set方法
  2. domain:不按mvc分层,只是java bean有一些属性,还有get set方法
  3. po:用在持久层,还可以再增加或者修改的时候,从页面直接传入action中,它里面的java bean 类名等于表名,
    属性名等于表的字段名,还有对应的get set方法
  4. vo: view object表现层对象,主要用于在高级查询中从页面接收传过来的各种参数.好处是扩展性强
  5. bo: 用在servie层,现在企业基本不用.

这些po,vo, bo,pojo可以用在各种层面吗?

可以,也就是po用在表现层,vo用在持久层不报错,因为都是普通的java bean没有语法错误.
但是在企业最好不要混着用,因为这些都是设计的原则,混着用比较乱.不利于代码维护.

Donate comment here