Mybatis
1. 概述
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 2013年11月迁移到Github。
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
具体操作(复习):
//注册驱动
Class.forName("com.mysql.jdbc.Driver"); //获取连接对象 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test? useUnicode=true&characterEncoding=utf8", "root", "root");
//编写
sql String sql = "select * from user where name=?"
//预编译
PreparedStatement statement =connection.prepareStatement(sql);
//设置参数
statement.setString(1, username);
//执行sql,封装结果
ResultSet resultSet = statement.executeQuery();
虽然传统JDBC也可以解决我们操作数据库的需求,但是它的功能简单并且耦合度高,如果我们在项目上线后修改某条sql语句,就需要去修改Java源码,并对项目进行重新的打包部署等一系列工作,操作起来相当的繁琐。
这里我们还需要介绍一个大名鼎鼎的全自动映射ORM框架:Hibernate
ORM: Object Relation Mapping 对象关系映射,简单说就是把数据库表和实体类的属性对应起来,让我们可以操作实体类就可以实现操作数据库表。
2.为什么使用MyBatis
JDBC
\1. SQL写在Java代码中,耦合度提高
\2. 实际开发中SQL会经常被更新,维护不易
Hibernate 和 JPA
\1. 长难复杂的SQL,对Hibernate而言处理也不容易
\2. 内部自动产生的SQL,不易做特殊优化
\3. 基于全映射的全自动框架,进行字段部分映射时比较困难,导致数据库性能下降
JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系
表的映射关系,并将运行期的实体对象持久化到数据库中
MyBatis
\1. SQL和Java编码分离,功能划分清晰,一个专注业务,一个专注数据。
\2. 核心SQL可以自己编写,优化比较方便
3.自定义MyBatis
3.1 分析
MyBatis在使用代理的方式实现增删改查时都做了什么事?
\1. 创建代理对象
\2. 在代理对象中创建方法
我们以入门案例分析MyBatis工作流程,之后我们再具体实现一下。
根据上图分析,我们如果想让代码完整执行下来我们需要提供两组信息:
\1. 连接信息
\2. 映射信息
执行的sql语句
封装结果的实体类全限定类名
随着项目功能的扩充,映射信息也会越来越多,所以我们需要使用对象来存储映射信息,那么怎么区分不同的映射信息呢,根据配置文件,我们知道,namespace + id 可以区分映射信息,所以我们可以考虑使用Map集合来存储这部分信息。
分析到这里,整个流程基本就完成了,还有一个难点就是如何使用动态代理创建代理对象和对接口中方法的实现。接下来我们再通过图解的方式给大家分析一下。
3.2 自定义MyBatis
首先我们在第一个项目中 查看以下几个名称对应的是接口还是类。
Resources :类
SqlSession :接口
SqlSessionFactory :接口
SqlSessionFactoryBuilder :类
4.MyBatis- CRUD操作
1.在UserDao中添加方法
/**
* 操作用户数据的接口
*/
public interface UserDao {
/**
* 查询所有用户信息
*/
List<User> findAll();
void addUser(User user);
/**
* 添加用户
* @param user
*/
void deleteUser(User user);
/**
* 修改信息
*/
void updateUser(User user);
/**
* 根据id查询用户信息
*/
User findUserById(int id);
/**
* 模糊查询
*
*/
List<User> findUserByName(String name);
/**
* 查询用户总记录数
*/
int findTotal();
/**
* 根据QueryVo查询数据信息
*/
List<User> findUserByVo(QueryVo queryVo);
}
2.在UserDao.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.wdzl.dao.UserDao"><!--对应dao层接口的全限定类名-->
<resultMap id="userMap" type="com.wdzl.pojo.User">
<id property="uid" column="id"></id>
<result property="username" column="username"></result>
</resultMap>
<!--查询所有用户信息的方法-->
<select id="findAll" resultMap="userMap">
select * from user
</select>
<!--添加用户-->
<insert id="addUser" parameterType="com.wdzl.pojo.User">
insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
</insert>
<!--修改信息-->
<update id="updateUser" parameterType="com.wdzl.pojo.User">
update user set address=#{address} where username=#{username}
</update>
<!--删除信息-->
<delete id="deleteUser" parameterType="com.wdzl.pojo.User">
delete from user where username=#{username};
</delete>
<!--模糊查询-->
<select id="findUserByName" parameterType="String" resultType="com.wdzl.pojo.User">
select * from user where username like #{username};
</select>
<!--根据id查询用户信息-->
<select id="findUserById" parameterType="java.lang.Integer" resultType="com.wdzl.pojo.User">
select *from user where id=#{id};
</select>
<!--查询用户总记录数-->
<select id="findTotal" resultType="int">
select count(id) from user
</select>
<!---->
<select id="findUserByVo" parameterType="com.wdzl.pojo.QueryVo" resultType="com.wdzl.pojo.User">
select * from user where username like #{user.username}
</select>
</mapper>
3.编写测试类
package com.wdzl.dao;
import com.wdzl.pojo.QueryVo;
import com.wdzl.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class UserDaoTest {
InputStream is;
SqlSessionFactoryBuilder builder;
SqlSessionFactory factory;
SqlSession sqlSession;
UserDao userDao;
@Before
public void init() throws IOException {
//1.读取配置文件
is = Resources.getResourceAsStream("SqlMapConfig");
//2.创建SqlSessionFactory工厂
builder = new SqlSessionFactoryBuilder();
factory =builder.build(is);
//3.用工厂生产SqlSession对象
sqlSession = factory.openSession();
//4.使用SqlSession对象创建dao层代理对象
userDao = sqlSession.getMapper(UserDao.class);
}
@After
public void destroy() throws IOException {
sqlSession.close();
is.close();
}
@Test
public void findAllTest() throws IOException {
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
//遍历
for (User user:users){
System.out.println(user);
}
}
@Test
public void addUserTest() throws IOException {
User user=new User(0,"赵童","男","2000-01-01","宝鸡");
// //4.使用SqlSession对象创建dao层代理对象
// userDao = sqlSession.getMapper(UserDao.class);
//5.使用代理对象执行方法
userDao.addUser(user);
System.out.println(user);
System.out.println("----------------------------------------");
//6.开启事务
sqlSession.commit();
System.out.println(user);
}
@Test
public void testUpdateUser(){
User user=new User();
user.setUsername("%童");
user.setAddress("西安");
List<User> userByName = userDao.findUserByName("%童");
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
//遍历
for (User user2:userByName){
System.out.println(user2);
}
}
@Test
public void testDeleteUser(){
User user=new User();
user.setUsername("赵童");
userDao.deleteUser(user);
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
//遍历
for (User user2:users){
System.out.println(user2);
}
}
@Test
public void testFindUserById(){
User user=userDao.findUserById(3);
System.out.println(user);
}
@Test
public void testFindTotal(){
int total = userDao.findTotal();
System.out.println(total);
}
@Test
public void testFindByVo() {
QueryVo queryVo = new QueryVo();
User user = new User();
user.setUsername("%德%");
queryVo.setUser(user);
List<User> users = userDao.findUserByVo(queryVo);
for (User user1 : users) {
System.out.println(user1);
}
}
}
参数详解
parameterType 输入类型
\1. 传递基本数据类型和 String
\2. 传递pojo对象
当传递参数为对象时,在sql语句中获取对象属性值需要使用OGNL表达式。
-
OGNL:Object Graphic Navigation Language 对象图导航语言
-
它是通过对象的取值方式来获取值,在写法上把get给省略了
在类中,我们获取值:user.getUsername();
在OGNL中:user.userName
Mybatis中为什么能直接写userName,而不加 user. 呢?
因为在 parameterType 中已经提供了属性所属的类,所以可以省略类名
使用包装类型作为参数类型
有时候我们的查询条件可能是符合条件,比如说查询条件需要用户信息,还需要其他信息,这些信息可以组成一个对象,这时候我们就可以考虑使用保障类来组合条件。
1.创建包装类QueryVo
/*** 传递参数的包装类
* @author lp
* @version 1.0
*/
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
2. 在接口中定义相应方法
/*** 根据QueryVo中的信息查询用户 */
List<User> findUserByVo(QueryVo vo);
3. 在配置文件UserDao.xml中进行配置
<!--根据包装类信息模糊查询用户数据-->
<select id="findUserByVo" parameterType="com.wdzl.domain.QueryVo" resultType="com.wdzl.domain.User"> select * from user where username like #{user.username};
</select>
4. 测试
/*** 测试查询功能-根据包装类信息查询用户信息 */
@Test
public void testVo() {
QueryVo vo = new QueryVo();
User user = new User();
user.setUsername("%周%");
vo.setUser(user);
List<User> users = userDao.findUserByVo(vo); for (User u : users) {
System.out.println(u);
}
}
.3 实体类属性与表字段不对应的问题解决
首先我们先将 User 类中的 id 属性名改为 userId ,重写类中的方法,重新执行查询功能,我们会
发现,查出来的数据,id项都为空,这是因为属性和数据库字段名无法匹配的原因,对于这些问题,我
们有3种解决方案。
方案1:统一实体类和表字段的名称(不做演示)
方案2:给数据库字段加别名 ,让别名与实体类属性名 统一(查询效率比较高)
我们以 根据包装类信息模糊查询用户信息 为例,对sql语句进行修改,并给不对应的列起一个别名。
<!--根据包装类信息模糊查询用户数据-->
<select id="findUserByVo" parameterType="com.wdzl.domain.QueryVo" resultType="com.wdzl.domain.User">
select id as userId,username,birthday,sex,address from user where username like #{user.username};
</select>
方案3:在UserDao.xml中配置对应关系
- 在配置文件UserDao.xml中添加映射配置信息
2.映射配置添加好以后,我们将对应的的 resultType 修改为 resultMap ,对应属性值为
- 执行相应测试,此时查询出来的信息中,id属性已经有了值,这就表示我们的配置信息已经配置成功。
5.MyBatis实现Dao层的开发
现实开发中,有时候也是需要我们自己去实现Dao层接口,那么如何使用MyBatis框架自定义实现类是我们接下来要研究的内容。
准备工作:
新建工程,将前面演示CRUD操作的工程内容复制到该工程中,并做对应的修改。
复制 main 和 test 目录
修改 pom.xml ,把相关依赖添加到配置文件中
删除 UserDao 中的 ,并将下面标签中使用到 的地方换成
resultType="com.wdzl.domain.User"
删除包装类 QueryVo 还有对应的 配置 和 测试
查询所有的方法
\1. 添加UserDao对应的实现类:先实现查询所有列表的方法。
package com.wdzl.dao.Impl;
import com.wdzl.dao.UserDao;
import com.wdzl.pojo.QueryVo;
import com.wdzl.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import java.util.List;
public class UserDaoImpl implements UserDao {
private SqlSessionFactory factory;
private SqlSession sqlSession;
public UserDaoImpl() {
}
public UserDaoImpl(SqlSessionFactory factory) {
this.factory = factory;
sqlSession = factory.openSession();
}
@Override
public List<User> findAll() {
//2.调用sqlSession 的方法
List<User> users = sqlSession.selectList("com.wdzl.dao.UserDao.findAll");
return users;
}
@Override
public void addUser(User user) {
//获取sqlSession对象
SqlSession sqlSession = factory.openSession();
//调用方法
sqlSession.insert("com.wdzl.dao.UserDao.addUser", user);
//提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
}
@Override
public void deleteUser(User user) {
//调用方法
sqlSession.delete("com.wdzl.dao.UserDao.deleteUser", user);
//提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
}
@Override
public void updateUser(User user) {
//获取sqlSession对象
SqlSession sqlSession = factory.openSession();
//调用方法
sqlSession.update("com.wdzl.dao.UserDao.updateUser", user);
//提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
}
@Override
public User findUserById(int id) {
User user = sqlSession.selectOne("com.wdzl.dao.UserDao.findUserById", id);
return user;
}
@Override
public List<User> findUserByName(String name) {
List<User> user = sqlSession.selectList("com.wdzl.dao.UserDao.findUserByName", name);
return user;
}
@Override
public int findTotal() {
Integer total= sqlSession.selectOne("com.wdzl.dao.UserDao.findTotal");
return total;
}
@Override
public List<User> findUserByVo(QueryVo queryVo) {
return null;
}
}
2.测试:只修改需要用的方法
package com.wdzl.dao;
import com.wdzl.dao.Impl.UserDaoImpl;
import com.wdzl.pojo.QueryVo;
import com.wdzl.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class UserDaoTest {
InputStream is;
SqlSessionFactoryBuilder builder;
SqlSessionFactory factory;
SqlSession sqlSession;
UserDao userDao;
@Before
public void init() throws IOException {
//1.读取配置文件
is = Resources.getResourceAsStream("SqlMapConfig");
//2.创建SqlSessionFactory工厂
builder = new SqlSessionFactoryBuilder();
factory =builder.build(is);
//3.用工厂生产SqlSession对象
sqlSession = factory.openSession();
//4.使用SqlSession对象创建dao层代理对象
userDao = sqlSession.getMapper(UserDao.class);
}
@After
public void destroy() throws IOException {
sqlSession.close();
is.close();
}
@Test
public void findAllTest() throws IOException {
UserDao userDao = new UserDaoImpl(factory);
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
//遍历
for (User user:users){
System.out.println(user);
}
}
@Test
public void testAddUser() {
User user = new User(0, "唐康", "男", "1999-1-1", "北京");
UserDao userDao = new UserDaoImpl(factory);
userDao.addUser(user);
}
@Test
public void testUpdateUser() {
User user = new User(0, "唐康", "null", "null", "西安");
UserDao userDao = new UserDaoImpl(factory);
userDao.updateUser(user);
}
@Test
public void testDeleteUser() {
User user = new User(5, "null", "null", "null", "null");
UserDao userDao = new UserDaoImpl(factory);
userDao.deleteUser(user);
}
@Test
public void testFindUserById() {
UserDao userDao = new UserDaoImpl(factory);
User user = userDao.findUserById(2);
System.out.println(user);
}
@Test
public void testFindUserByName() {
UserDao userDao = new UserDaoImpl(factory);
List<User> user = userDao.findUserByName("赵童");
System.out.println(user);
}
@Test
public void testFindTotal() {
UserDao userDao = new UserDaoImpl(factory);
int total = userDao.findTotal();
System.out.println(total);
}
}
properties标签
**方式一:**将配置提取出来进行统一管理,在需要使用的地方使用 ${} 引用即可
方式二:引用外部配置文件
引用外部有两个属性:
resource : 用于指定配置文件的位置类路径的写法,并且必须存在于类路径下。
url : 统一资源定位符。可以标识资源的位置Http : // localhost : 8080 /book/index.jsp
协议 主机地址 端口 URI
在windows文件夹中使用的是fifile协议,我们可以将配置文件所在目录拖入浏览器,就可以看到文件在文件系统中的rul
6.typeAliases标签 和package标签
在之前的演示案例中, UserDao.xml 配置文件可以配置映射关系,比如下图标签中,resultType 标签的属性值,可以使 int,java.lang.Integer ,也可以是 INT ,这是因为MyBatis框架给这些标签起了别名,所以这几种属性值才可以表示相同的类。
下面我们来看如何给实体类配置别名:在 主配置文件中使用 typeAliases 配置别名,它只能给实体类配置别名。
在 typeAliases 中 有两个属性
type:用来指定起别名的实体类,如果省略 alias 属性,则使用默认别名,默认别名为类名 ,不区分大小写,任意组合都可识别。
alias:指定一个新的别名,比如上图案例中将别名设置为 hehe ,则在下面使用时使用 hehe 即可表示User类。新的别名也不区分大小写。
当实体类比较多时,我们可以使用 package 标签批量给包中的类添加别名
连接池
概述
连接池就是用于存储连接对象的一个容器,当系统初始化完成后,容器被创建,容器会申请一些连
接对象,当用户访问数据库时,从容器中获取连接对象即可。访问完之后,会将连接对象归还给连接
池。这样做可以提高效率,提高连接对象的复用,不必频繁的创建和释放连接对象。
MyBatis连接池
连接池有很多种,最为熟悉的比如c3p0,DBCP,druid等。
MyBatis支持三种内置的数据源类型:
POOLED: 实现dataSource接口,并且使用了池的思想。
UNPOOLED: 同样也是实现了dataSource接口,但是该类型并没有使用池的思想。
JDNI: 采用服务器提供的JDNI技术实现的,并且在不同服务器之间获取的连接池是不一样的。
当前框架教学使用的是Tomcat服务器,采用的是dbcp连接池技术
通过控制台的信息查看内置连接池
事务
对于事务的控制后期我们主要依赖于Spring来完成,当然MyBatis本身也是可以对事物进行控制的。我们查看演示案例执行 addUser 方法时,我们会发现,Mybatis框架 默认事务提交方式是手动的。