mybatis第一阶段

mybatis第一阶段

什么是mybits

  • mybits是apache的一个持久层的框架,之前叫做ibatis,后来代码迁移到google code上,现在代码是在github上.

  • MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。

  • Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatement、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。

Mybits的入门

  • 企业建包的时候实体类用 pojo 而不用domain,原因是domain不规范,只能测试用,正式的是 pojo

JDBC中存在的一些问题

  1. 频繁创建释放资源,比较浪费
  2. sql语句在代码中硬编码不利于维护
  3. 传入参数硬编码在代码中不利于维护
  4. 遍历结果硬编码不利于维护
  • 复习JDBC
    1. 加载数据库驱动
    2. 创建并获取数据库链接
    3. 创建jdbc statement对象
    4. 设置sql语句
    5. 设置sql语句中的参数(使用preparedStatement)
    6. 通过statement执行sql并获取结果
    7. 对sql执行结果进行解析处理
    8. 释放资源(resultSet、preparedstatement、connection)

Mybatis的架构

  1. mybatis配置

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

  3. 由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。sqlSession线程是不安全的,所以把他最好放在局部方法中
  4. mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
  5. Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statementid
  6. Mapped Statement对sql执行输入参数进行定义,包括HashMap基本类型pojoExecutor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。
  7. Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。

Mybatis的使用

  1. 下载jar包在github上,核心包跟依赖包,还需要连接数据库的包https://github.com/mybatis/mybatis-3/releases
  2. 创建一个congif的文件夹,创建核心配置文件
    • pooled:mybatis的连接池
  3. 创建映射文件
    • namespace命名空间:
    • 引入约束:

入门程序:

  • 手动引入log4j跟以前的不一样:

    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
  • 需求:

    • 根据用户id查询用户
    • 根据用户名模糊查询用户信息列表
    • 添加用户
    • 修改用户
    • 删除用户
  • 核心配置文件约束:

1
2
3
4
5
<?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>
  • 映射配置文件约束:
1
2
3
4
5
6
<?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="test">
</mapper>

根据用户id查询用户

  • 外部资源文件jdbc.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=123
  • 编写核心配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<configuration>
<!-- 引入存放用户名密码的资源文件 -->
<properties resource="jdbc.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>
  • 编写映射配置文件
    • namespace:命名空间,用于隔离sql语句,后面会讲另一层非常重要的作用。
    • id:sql语句的唯一标识
    • parameterType: 指定传入参数类型
    • resultType:指定返回值类型
    • #{}占位符, 起到占位的作用, 如果传入的参数类型为简单类型(String, double, long, boolean,integer等), 那么#{}中的变量名称可以随便写
1
2
3
4
5
<mapper namespace="test">
<select id="findUserById" parameterType="java.lang.Integer" resultType="cn.itcast.pojo.User">
select * from user where id=#{id}
</select>
</mapper>
  • mybatis框架需要加载映射文件,将Users.xml添加在SqlMapConfig.xml,如下:
1
2
3
<mappers>
<mapper resource="sqlmap/User.xml"/>
</mappers>
  • 测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
//加载核心配置文件
String resource = "sqlMapConfig.xml";
//把核心配置文件通过流读入
InputStream inputStream = Resources.getResourceAsStream(resource);
//根据核心配置文件的流来创建会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//获取会话
SqlSession openSession = factory.openSession();
//通过会话执行sql语句, 第一个参数: 调用的sql语句, namespace+.+sql语句id, 第二个参数为传入sql语句的参数
//当整个配置文件中只有唯一的一个会话的时候可以省略前面的namespace
User user = openSession.selectOne("test.findUserById", 1);
System.out.println(user);

根据用户名字模糊查询用户信息

如果查询结果返回集合, 那么可以调用selectList方法, selectList方法返回的结果就是一个集合, 那么mybatis不知道里面的泛型是什么,所以需要配置泛型的类型
${}拼接符, 作用是字符串原样拼接, 如果传入的参数是简单类型(String, double, long, boolean,integer等),那么${}中的变量名称必须是value
-注意: 如果使用${}拼接符有可能造成sql注入的风险

  • 映射配置文件

    1
    2
    3
    <select id="findUserByUserName" parameterType="java.lang.String" resultType="cn.itcast.pojo.User">
    select * from user where username like '%${value}%'
    </select>
  • 测试

1
2
3
4
5
6
7
8
9
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//创建会话
SqlSession openSession = factory.openSession();
List<User> userList = openSession.selectList("test.findUserByUserName", "张");
System.out.println(userList);

插入用户

  • 配置文件
1
2
3
<insert id="insertUser" parameterType="cn.itcast.pojo.User">
INSERT INTO user (username, birthday, sex, address) values(#{username},#{birthday},#{sex},#{address})
</insert>
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void testInsertUser() throws Exception {
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//创建会话
SqlSession openSession = factory.openSession();
User user = new User();
user.setUsername("老王");
user.setSex("1");
user.setBirthday(new Date());
user.setAddress("北京昌平");
//执行添加
openSession.insert("test.insertUser", user);
//提交事务, mybatis会替我们自动开启事务,但是不知道在哪行代码后提交, 所以需要我们手动提交事务
openSession.commit();
System.out.println(user);
}
自增主键返回
  • 通过修改sql映射文件,可以将mysql自增主键返回:
1
2
3
4
5
6
7
8
select LAST_INSERT_ID()mysql数据库函数, 用来返回最后增加的数据的主键id
keyProperty: 将查询出的主键数据放入传入参数User对象中的id属性中保存
order相对于insert语句的执行顺序, 在insert前执行是before, 在insert后执行是after,由于mysql的自增原理执行完insert语句之后才将主键生成,所以这里selectKey的执行顺序为after
resultType: keyProperty中指定属性的类型
-->
<selectKey keyProperty="id" order="after" resultType="java.lang.Integer">
select LAST_INSERT_ID()
</selectKey>
Mysql使用 uuid实现主键
  • 需要增加通过select uuid()得到uuid值
    • 注意这里使用的order是“BEFORE”
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>

修改

  • 配置文件
1
2
3
<update id="updateUserById" parameterType="cn.itcast.pojo.User">
update user set username=#{username} where id=#{id}
</update>
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//创建会话
SqlSession openSession = factory.openSession();
User user = new User();
user.setUsername("老李");
user.setId(28);
//调用sql语句执行
openSession.update("test.updateUserById", user);
//提交
openSession.commit();

删除

  • 配置文件
1
2
3
<delete id="delUserById" parameterType="int">
delete from user where id=#{id}
</delete>
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//创建会话
SqlSession openSession = factory.openSession();
//调用sql语句执行删除
openSession.delete("test.delUserById", 29);
//提交
openSession.commit();

核心配置文件加载映射文件的几种方式:

mappers里面配置:

  • mapper

    • url:不推荐使用, 因为如果配置url就要写映射文件的绝对路径, 这样部署到linux系统的时候需要更改, 所以不方便
    • resource:映射文件的相对路径
    • class:接口的全路径名:一般动态代理开发的时候使用这种方式
      • 1.要求接口文件和映射文件在同一个目录下
      • 2.要求接口文件和映射文件除扩展名外名称必须完全一样
  • package:通过包扫描的方式批量引入mapper,name:指定包的路径名称
    <package name="cn.itcast.mapper"/>

Dao的生成方式

原生dao开发

需要程序员编写dao接口跟实现类

  • 编写接口DAO

    1
    2
    3
    4
    5
    6
    public interface UserDao {
    public User findUserbyId(Integer id);
    public List<User> findUserByName(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
private SqlSessionFactory factory;
//工厂由外部传入
public UserDaoImpl(SqlSessionFactory factory) {
this.factory = factory;
}
@Override
public User findUserbyId(Integer id) {
//会话是线程不安全的, 所以它的最佳使用范围在方法体内
SqlSession openSession = factory.openSession();
User user = openSession.selectOne("test.findUserById", id);
return user;
}
@Override
public List<User> findUserByName(String userName) {
//会话是线程不安全的, 所以它的最佳使用范围在方法体内
SqlSession openSession = factory.openSession();
List<User> list = openSession.selectList("test.findUserByUserName", userName);
return list;
}
  • 测试:用单元测试的@Before注解,先把SessionFactory实例化
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
private SqlSessionFactory factory;
//@Before是在@Test之前执行
@Before
public void init() throws Exception{
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserById() throws Exception {
//实例化userDao
UserDao userDao = new UserDaoImpl(factory);
User user = userDao.findUserbyId(1);
System.out.println(user);
}
@Test
public void testFindUserByUserName() throws Exception{
//实例化userDao
UserDao userDao = new UserDaoImpl(factory);
List<User> list = userDao.findUserByName("张");
System.out.println(list);
}

动态代理的开发方式

需要程序员编写dao接口,实现类由Mybaits生成

  • 一般就不用dao作为包名了,用 mapper
  • 接口

    1
    2
    3
    4
    public interface UserMapper {
    public User findUserById(Integer id);
    public List<User> findUserByUserName(String name);
    }
  • 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <mapper namespace="cn.itcast.mapper.UserMapper">
    <!--
    id:sql语句的唯一标识
    parameterType: 指定传入参数类型
    resultType:指定返回值类型
    -->
    <select id="findUserById" parameterType="int" resultType="cn.itcast.pojo.User">
    select * from user where id=#{id}
    </select>
    <select id="findUserByUserName" parameterType="java.lang.String" resultType="cn.itcast.pojo.User">
    select * from user where username like '%${value}%'
    </select>
    </mapper>

动态代理dao开发方式规范:

  1. 映射文件中namespace必须等于接口的全路径名称
  2. 映射文件中的sql语句id必须等于接口中的方法名称
  3. 映射文件中传入参数类型parameterType指定类型, 必须等于接口中方法的传入参数类型
  4. 映射文件中的返回的结果集类型必须等于接口方法的返回值类型
  • 引入映射文件的时候用<package class="mapper"/>

生成的实现类在内存中找不到,

SqlMapConfig.xml文件说明

配置的时候各个配置是有顺序的,不匹配会报错

  • properties :里面有resourse意思是引入配置文件例如JDBC的用户名跟密码
  • typeAliases:给单个类起别名
    • typeAlias<typeAlias type="cn.itcast.pojo.User" alias="user"/>
      • type: 起别名的pojo的全路径名
      • alias: 自定义的别名
    • package:使用包扫描的方式批量给整个包下面的pojo起别名<package name="cn.itcast.pojo"/>
      • name:包的全路径名称
      • 别名就是类名, 不区分大小写, 推荐根据java命名规则来使用别名

#{}和${}的区别

  • #{}表示一个占位符号

    • 通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换
    • #{}可以有效防止sql注入。
    • #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
  • ${}表示拼接sql串,例如模糊查询的时候使用

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

parameterType和resultType

  • parameterType:指定输入参数类型,mybatis通过ognl从输入对象中获取参数值拼接在sql中。
  • resultType:指定输出结果类型,mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。

测试类中的selectOne和selectList

  • selectOne查询一条记录,如果使用selectOne查询多条记录则抛出异常:
1
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:70)
  • selectList可以查询一条或多条记录。

hibernate和mybatis区别:

  • hibernate:
    • hibernate是一个orm框架,需要编写hql语句,自动化程度高,但是学习成本也高,使用复杂,但是写代码效率高,开发速度快.
    • 使用场景:一般外包公司使用较多,因为开发速度快,一些传统项目也在用,oa,crm,erp
  • mybatis:
    • mybatis不是一个完全的orm框架,需要编写sql语句,学习成本低,比较简单。
    • 使用场景:在互联网企业用的一般都是,电商,互联网金融等。
张冲 wechat
欢迎扫一扫上面的微信关注我,一起交流!
坚持原创技术分享,您的支持将鼓励我继续创,点击打赏!