Mybatis笔记
一、什么是Mybatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
二、Mybatis配置文件
Mapper文件
<?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="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
sql映射文件:写sql语句的,mybatis会执行这些sql
1、指定约束文件
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
mybatis-3-mapper.dtd是约束文件的名称,扩展名是dtd的。
2、约束文件的作用:
- 限制,检查当前文件中出现的标签属性必须符合mybatis的要求。
3、
- mapper是当前文件的根标签,必须的。
- namespace:叫做命名空间,唯一值的,可以自定义的字符串。但一般要求你使用dao接口的全限定名 称。
4、在当前文件中,可以使用特定的标签,表示数据库的特定操作。
<select>
:表示执行查询,select语句。
<update>
:表示更新数据库的操作,就是在标签中写的是update语句。
<insert>
:表示插入,放的是insert语句。
<delete>
:表示删除,执行的delete语句。
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
5、
- select:表示查询操作。
- id:你要执行的sql语法的唯一要求,mybatis会使用这个id的值来找到要执行的sql语句,可以自定义,但要求你使用接口中的方法名称。
- resultType:表示结果类型的,是sql语句执行后得到的ResultSet,遍历这个ResultSet得到Java对象的类型。值写的是类型的全限定名称。
三、Mybatis中对象的介绍
主要类的介绍
1、Resources:mybatis中的一个类,负责读取配置文件
```java
InputStream in = Resources.getResourcesAsStream("mybatis.xml");
```
2、SqlSessionFactoryBuilder:创建SqlSessionFactory对象,
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(in);
3、SqlSessionFactory:重量级对象,程序创建一个对象耗时比较长,使用的资源比较多。在整个项
目中,有一个就够用了。
SqlSessionFactory:接口,接口实现类:DefaultSqlSessionFactory
SqlSessionFactory作用:获取SqlSession对象。SqlSession sqlSession = factory.openSession();
openSession()方法说明:
1)、openSession():无参数,获取是非自动提交事务的SqlSession对象
2)、openSession(boolean):
openSession(true) 获取自动提交事务的SqlSession对象。
openSession(false) 非获取自动提交事务的SqlSession对象。
4、SqlSession:
SqlSession接口:定义了操作数据的方法,例如 selectOne(), seleceList(), insert(),update(),
delete(),commit(),rollback()。
SqlSession接口的实现类DefaultSqlSession。
使用要求:SqlSession对象不是线程安全的,需要在方法内部使用,在执行sql语句之前,使用openSession()获取SqlSession对象,在执行完sql语句后,需要关闭它,执行SqlSession.close()。这样能保证它的使用是线程安全的。
四、Mybatis中的动态代理
在开始动态代理的原理讲解以前,我们先看一下集成mybatis以后dao层不使用动态代理以及使用动态代理的两种实现方式,通过对比我们自己实现dao层接口以及mybatis动态代理可以更加直观的展现出mybatis动态代理替我们所做的工作,有利于我们理解动态代理的过程,讲解完以后我们再进行动态代理的原理解析,此讲解基于mybatis的环境已经搭建完成,并且已经实现了基本的用户类编写以及用户类的Dao接口的声明,下面是Dao层的接口代码
public interface UserDao {
/*
查询所有用户信息
*/
List<User> findAll();
/**
* 保存用户
* @param user
*/
void save(User user);
/**
* 更新用户
* @return
*/
void update(User user);
/**
* 删除用户
*/
void delete(Integer userId);
/**
* 查找一个用户
* @param userId
* @return
*/
User findOne(Integer userId);
/**
* 根据名字模糊查询
* @param name
* @return
*/
List<User> findByName(String name);
/**
* 根据组合对象进行模糊查询
* @param vo
* @return
*/
List<User> findByQueryVo(QueryVo vo);
}
1、Mybatis dao层两种实现方式的对比
1.1 dao层不使用动态代理
dao层不使用动态代理的话,就需要我们自己实现dao层的接口,为了简便起见,我只是实现了Dao接口中的findAll方法,以此方法为例子来展现我们自己实现Dao的方式的情况,让我们来看代码:
public class UserDaoImpl implements UserDao{
private SqlSessionFactory factory;
public UserDaoImpl(SqlSessionFactory factory){
this.factory = factory;
}
public List<User> findAll() {
//1.获取sqlSession对象
SqlSession sqlSession = factory.openSession();
//2.调用selectList方法
List<User> list = sqlSession.selectList("com.example.dao.UserDao.findAll");
//3.关闭流
sqlSession.close();
return list;
}
public void save(User user) {
}
public void update(User user) {
}
public void delete(Integer userId) {
}
public User findOne(Integer userId) {
return null;
}
public List<User> findByName(String name) {
return null;
}
public List<User> findByQueryVo(QueryVo vo) {
return null;
}
这里的关键代码 List list = sqlSession.selectList(“com.example.dao.UserDao.findAll”),需要我们自己手动调用SqlSession里面的方法,基于动态代理的方式最后的目标也是成功的调用到这里。
注意:如果是添加,更新或者删除操作的话需要在方法中增加事务的提交。
1.2 dao层使用Mybatis的动态代理
使用动态代理的话Dao层的接口声明完成以后只需要在使用的时候通过SqlSession对象的getMapper方法获取对应Dao接口的代理对象,关键代码如下:
//3.获取SqlSession对象
SqlSession session = factory.openSession();
//4.获取dao的代理对象
UserDao mapper = session.getMapper(UserDao.class);
//5.执行查询所有的方法
List<User> list = mapper.findAll();
获取到dao层的代理对象以后通过代理对象调用查询方法就可以实现查询所有用户列表的功能。
2、Mybatis动态代理实现方式的原理解析
动态代理中最重要的类:SqlSession、MapperProxy、MapperMethod,下面开始从入口方法到调用结束的过程分析。
1)调用方法的开始:
//4.获取dao的代理对象
UserDao mapper = session.getMapper(UserDao.class); 因为SqlSesseion为接口,所以我们通过Debug方式发现这里使用的实现类为DefaultSqlSession。
2)找到DeaultSqlSession中的getMapper方法,发现这里没有做其他的动作,只是将工作继续抛到了Configuration类中,Configuration为类不是接口,可以直接进入该类的getMapper方法中
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
3)找到Configuration类的getMapper方法,这里也是将工作继续交到MapperRegistry的getMapper的方法中,所以我们继续向下进行。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
4)找到MapperRegistry的getMapper的方法,看到这里发现和以前不一样了,通过MapperProxyFactory的命名方式我们知道这里将通过这个工厂生成我们所关注的MapperProxy的代理类,然后我们通过mapperProxyFactory.newInstance(sqlSession);进入MapperProxyFactory的newInstance方法中
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
5)找到MapperProxyFactory的newIntance方法,通过参数类型SqlSession可以得知,上面的调用先进入第二个newInstance方法中并创建我们所需要重点关注的MapperProxy对象,第二个方法中再调用第一个newInstance方法并将MapperProxy对象传入进去,根据该对象创建代理类并返回。这里已经得到需要的代理类了,但是我们的代理类所做的工作还得继续向下看MapperProxy类。
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
6)找到MapperProxy类,发现其确实实现了JDK动态代理必须实现的接口InvocationHandler,所以我们重点关注invoke()方法,这里看到在invoke方法里先获取MapperMethod类,然后调用mapperMethod.execute(),所以我们继续查看MapperMethod类的execute方法。
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
@UsesJava7
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
return constructor
.newInstance(declaringClass,
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
/**
* Backport of java.lang.reflect.Method#isDefault()
*/
private boolean isDefaultMethod(Method method) {
return ((method.getModifiers()
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
&& method.getDeclaringClass().isInterface();
}
}
7)找到类MapperMethod类的execute方法,发现execute中通过调用本类中的其他方法获取并封装返回结果,我们来看一下MapperMethod整个类。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
8)MapperMethod类是整个代理机制的核心类,对SqlSession中的操作进行了封装使用。 该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装CRUD操作,也就是我们在xml中配置的操作的节点。每个节点都会生成一个MappedStatement类。MethodSignature用来封装方法的参数以及返回类型,在execute的方法中我们发现在这里又回到了SqlSession中的接口调用,和我们自己实现UerDao接口的方式中直接用SqlSession对象调用DefaultSqlSession的实现类的方法是一样的,经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。
3、使用动态代理时的注意事项
总结
什么是动态代理:
mybayis帮你创建dao接口的实现类,在实现类中调用SqlSession的方法执行sql语句
使用动态代理方式:
1、获取SqlSession对象,SqlSessionFactory.openSession()
2、使用getMapper方法获取某个接口的对象,sqlSession.getMapper(接口.class)
3、使用dao接口的方法,调用方法就执行了mapper文件中的sql语句
使用动态代理方式的要求:
1、dao接口和mapper文件放在一起,同一个目录
2、dao接口和mapper文件名称一致
3、mapper文件中的namespace的值是dao接口的全限定类名
4、mapper文件中的,,,等的id是接口中的方法名称
5、dao接口中不要使用重载方法,不要使用同名的,不同参数的方法
五、Mybatis中的参数理解
1、Mybatis传递参数的四种方式
方式一、顺序传递参数
mapper.java文件:
public User selectUser(String name, int deptId);
mapper.xml文件:
<select id="selectUser" resultType="com.wyj.entity.po.User">
select * from user where userName = #{0} and deptId = #{1}
</select>
注意:里面的数字代表你传入参数的顺序,不是特别建议使用这种方法传递参数,特别是参数个数多的时候。
方式二、注解@Param传递参数
mapper.java文件:
public User selectUser(@Param("userName") String name, int @Param("deptId") id);
mapper.xml文件:
<select id="selectUser" resultType="com.wyj.entity.po.User">
select * from user where userName = #{userName} and deptId = #{deptId}
</select>
注意:在xml文件中就只能以在@Param注解中声明的参数名称获取参数。
方式三、使用Map集合传递参数
mapper.java文件:
public User selectUser(Map<String, Object> params);
mapper.xml文件:
<select id="selectUser" parameterType="java.util.Map" resultType="com.wyj.entity.po.User">
select * from user where userName = #{userName} and deptId = #{deptId}
</select>
方式四、使用JavaBean实体类传递参数
mapper.java文件:
public User selectUser(User user);
mapper.xml文件:
<select id="selectUser" parameterType="com.wyj.entity.po.User" resultType="com.wyj.entity.po.User">
select * from user where userName = #{userName} and deptId = #{deptId}
</select>
2、#和$
-
#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by “111”, 如果传入的值是id,则解析成的sql为order by “id”.
-
$将传入的数据直接显示生成在sql中。如:order by u s e r i d user_id userid,如果传入的值是111,那么解析成sql时的值为order by user_id, 如果传入的值是id,则解析成的sql为order by id.
-
#方式能够很大程度防止sql注入。
-
$方式无法防止Sql注入。
-
$方式一般用于传入数据库对象,例如传入表名。
-
一般能用#的就别用$。
MyBatis排序时使用order by 动态参数时需要注意,用$而不是#
字符串替换
默认情况下,使用#{}格式的语法会导致MyBatis创建预处理语句属性并以它为背景设置安全的值(比如?)。这样做很安全,很迅速也是首选做法,有时你只是想直接在SQL语句中插入一个不改变的字符串。比如,像ORDER BY,你可以这样来使用:
ORDER BY ${columnName}
这里MyBatis不会修改或转义字符串。
重要:接受从用户输出的内容并提供给语句中不变的字符串,这样做是不安全的。这会导致潜在的SQL注入攻击,因此你不应该允许用户输入这些字段,或者通常自行转义并检查。
3、resultType和resultMap
resultType:当使用resultType做SQL语句返回结果类型处理时,对于SQL语句查询出的字段在相应的pojo中必须有和它相同的字段对应,而resultType中的内容就是pojo在本项目中的位置。
因此对于单表查询的话用resultType是最合适的。但是,如果在写pojo时,不想用数据库表中定义的字段名称,也是可以使用resultMap进行处理对应的。多表连接查询时,若是一对一的连接查询,那么需要新建一个pojo,pojo中包括两个表中需要查询出的所有的字段,这个地方的处理方式通常为创建一个继承一个表字段的pojo,再在里面添加另外一个表内需要查询出的字段即可。若是一对多查询时,若是使用内连接查询,则很可能出现查询出的字段有重复。使用双重for循环嵌套处理即可。
4、设置别名
<typeAliases>
<!--
第一种设置别名的方式:
可以指定一个类型一个自定义别名
type:自定义类型的全限定名称
alias:别名(短小,容易记忆)
-->
<typeAlias type="com.ithime.domain.Student" alias="student"/>
<!--
第二种设置别名的方式:
<package> name是包名,这个包中的所有类,类名就是别名(类名不区分大小写)
-->
<package name="com.ithime.domain"/>
</typeAliases>