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中存在的一些问题
- 频繁创建释放资源,比较浪费
- sql语句在代码中硬编码不利于维护
- 传入参数硬编码在代码中不利于维护
遍历结果硬编码不利于维护
- 复习JDBC
- 加载数据库驱动
- 创建并获取数据库链接
- 创建jdbc statement对象
- 设置sql语句
- 设置sql语句中的参数(使用preparedStatement)
- 通过statement执行sql并获取结果
- 对sql执行结果进行解析处理
- 释放资源(
resultSet、preparedstatement、connection
)
- 复习JDBC
Mybatis的架构
mybatis配置
SqlMapConfig.xml ,此文件作为
mybatis的全局配置文件,配置了
mybatis`的运行环境等信息。mapper.xml
文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml
中加载。
通过
mybatis
环境等配置信息构造SqlSessionFactory
即会话工厂- 由会话工厂创建
sqlSession
即会话,操作数据库需要通过sqlSession
进行。sqlSession线程是不安全的,所以把他最好放在局部方法中 mybatis
底层自定义了Executor
执行器接口操作数据库,Executor
接口有两个实现,一个是基本执行器、一个是缓存执行器。Mapped Statement
也是mybatis
一个底层封装对象,它包装了mybatis
配置信息及sql映射信息
等。mapper.xml
文件中一个sql对应一个Mapped Statement
对象,sql的id即是Mapped statement
的id
。Mapped Statement
对sql执行输入参数进行定义,包括HashMap
、基本类型
、pojo
,Executor
通过Mapped Statement
在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement
设置参数。Mapped Statement
对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
Mybatis的使用
- 下载jar包在github上,核心包跟依赖包,还需要连接数据库的包
https://github.com/mybatis/mybatis-3/releases
- 创建一个congif的文件夹,创建核心配置文件
- pooled:mybatis的连接池
- 创建映射文件
- namespace命名空间:
- 引入约束:
入门程序:
- 手动引入log4j跟以前的不一样:
# 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查询用户
- 根据用户名模糊查询用户信息列表
- 添加用户
- 修改用户
- 删除用户
核心配置文件约束:
<?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>
- 映射配置文件约束:
<?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
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8
jdbc.username=root
jdbc.password=123
- 编写核心配置文件
<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等), 那么#{}中的变量名称可以随便写
<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,如下:
<mappers>
<mapper resource="sqlmap/User.xml"/>
</mappers>
- 测试:
//加载核心配置文件
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注入的风险
- 映射配置文件
<select id="findUserByUserName" parameterType="java.lang.String" resultType="cn.itcast.pojo.User">
select * from user where username like '%${value}%'
</select>
- 测试
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);
插入用户
- 配置文件
<insert id="insertUser" parameterType="cn.itcast.pojo.User">
INSERT INTO user (username, birthday, sex, address) values(#{username},#{birthday},#{sex},#{address})
</insert>
- 测试
@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自增主键返回:
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”
<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>
修改
- 配置文件
<update id="updateUserById" parameterType="cn.itcast.pojo.User">
update user set username=#{username} where id=#{id}
</update>
- 测试
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();
删除
- 配置文件
<delete id="delUserById" parameterType="int">
delete from user where id=#{id}
</delete>
- 测试
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
public interface UserDao {
public User findUserbyId(Integer id);
public List<User> findUserByName(String userName);
}
- 实现类
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实例化
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
- 接口
public interface UserMapper {
public User findUserById(Integer id);
public List<User> findUserByUserName(String name);
}
- 配置文件
<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开发方式规范:
- 映射文件中namespace必须等于接口的全路径名称
- 映射文件中的sql语句id必须等于接口中的方法名称
- 映射文件中传入参数类型parameterType指定类型, 必须等于接口中方法的传入参数类型
映射文件中的返回的结果集类型必须等于接口方法的返回值类型
- 引入映射文件的时候用
<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命名规则来使用别名
- typeAlias
#{}和${}的区别
#{}
表示一个占位符号- 通过
#{}
可以实现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
查询多条记录则抛出异常:
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语句,学习成本低,比较简单。
- 使用场景:在互联网企业用的一般都是,电商,互联网金融等。