什么是Mybatis?
Mybatis是一个半自动化的持久层框架。
Mybatis可以将向PreparedStatement中的输入参数自动进行映射(输入映射),将结果集映射成Java对象(输出映射)
为什么使用Mybatis?
JDBC:
SQL夹杂在Java代码块中,耦合度高导致硬编码
维护不易且实际开发需求中SQL有变化,频繁修改的情况多见
Hibernate和JPA:
长难复杂SQL,对于Hibernate而言处理也不容易
内部自动生成的SQL,不容易做特殊优化
基于全映射的全自动框架,大量字段的POJO进行部分映射时比较苦难,导致数据库性能下降
而实际开发中,对开发人员而言,核心SQL还是需要自己优化,而Mybatis中SQL和Java代码分开,功能边界清晰,一个专注业务,一个专注数据
配置文件,mybatis-config.xml
<?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> <properties resource=""></properties><!--加载配置文件--> <settings> <!--开启二级缓存,默认开启--> <setting name="cacheEnabled" value="true"/> </settings> <typeAliases> <!--设置单个pojo别名--> <!--<typeAlias alias="Employee" type="com.yang.domain.Employee"/>--> <!--对整个包下的pojo设置别名,别名为类名,如果类上使用了@Alias("")注解指定了别名则用注解设置的--> <package name="com.yang.domain"/> </typeAliases> <!--与Spring整合后,environment配置将废除--> <environments default="development"> <environment id="development"> <!--使用jdbc事务管理,由mybatis自己管理--> <transactionManager type="JDBC"></transactionManager> <!--数据库连接池,由mybatis自己管理--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test"/> <property name="username" value="yang"/> <property name="password" value="yang"/> </dataSource> </environment> </environments> <!--我们写的sql映射文件--> <mappers> <mapper resource="mybatis/xxxMapper.xml"/> </mappers> </configuration>
logback.xml,打印sql
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="/logback/LogFile"/>
<!--控制台输出-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{yyyy-MM-dd HH:mm:ss.SSS}|%thread|%-5level|%r|%X{threadId}|%C|%msg%n</pattern>
</encoder>
</appender>
<!--sql相关-->
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
简单的Mybatis操作数据库步骤:
1:创建Mybatis全局配置文件,包含了影响Mybatis行为的设置(setting)和属性(properties)信息、如数据库连接池信息等
2:创建SQL映射文件,映射文件的作用就相当于是定义Dao接口的实现类如何工作
3:将sql映射文件注册到全局配置中
4:持久化代码
1):根据全局配置文件得到SqlSessionFactory
2):使用SqlSessionFactory,获取到SqlSession对象使用它来执行增删改查,一个SqlSession就是代表和数据库的一次会话,用完则关闭
3):使用sql的唯一标志,namespace+id,执行sql
String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 1、获取sqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 2、获取sqlSession对象 SqlSession openSession = sqlSessionFactory.openSession(); try { // 3、获取接口的实现类对象 //会为接口自动的创建一个代理对象,代理对象去执行增删改查方法 Employee employee = (Employee) openSession.selectOne( "com.atguigu.mybatis.EmployeeMapper.selectEmp", 1); } finally { openSession.close(); }
第二种方式,接口式编程
xxxMapper.xml中的namespace设置为xxxMapper接口的全路径名,Mybatis会为Mapper接口创建一个代理对象
使用接口式编程会有更强的类型检查,参数控制等
String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 1、获取sqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 2、获取sqlSession对象 SqlSession openSession = sqlSessionFactory.openSession(); try { // 3、获取接口的实现类对象 //会为接口自动的创建一个代理对象,代理对象去执行增删改查方法 EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class); Employee employee = mapper.getEmpById(1); } finally { openSession.close(); }
SqlSession,需要注意:
1、SqlSession的实例不是线程安全的,因此是不能被共享的
2、SqlSession每次使用完成后需要正确关闭,这个关闭操作是必须的
3、SqlSession可以直接调用方法的id进行数据库操作,不过一般推荐使用SqlSession获取到Dao接口的代理类,执行代理对象的方法,可以更安全的进行类型检查操作
代理Mapper执行方法的源码:
1、JDK动态代理创建Mapper的代理类
public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) { ClassLoader classLoader = mapperInterface.getClassLoader(); Class<?>[] interfaces = new Class[]{mapperInterface}; MapperProxy proxy = new MapperProxy(sqlSession); return Proxy.newProxyInstance(classLoader, interfaces, proxy); }
2、代理类执行方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } else { Class<?> declaringInterface = this.findDeclaringInterface(proxy, method); MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, this.sqlSession); Object result = mapperMethod.execute(args); if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) { throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } else { return result; } } }
execute:
public Object execute(Object[] args) { Object result = null; Object param;
// 判断执行sql类型,insert,update、delete或select,然后封装参数,调用的还是sqlSession的增删改查方法 if (SqlCommandType.INSERT == this.type) { param = this.getParam(args); result = this.sqlSession.insert(this.commandName, param); } else if (SqlCommandType.UPDATE == this.type) { param = this.getParam(args); result = this.sqlSession.update(this.commandName, param); } else if (SqlCommandType.DELETE == this.type) { param = this.getParam(args); result = this.sqlSession.delete(this.commandName, param); } else { if (SqlCommandType.SELECT != this.type) { throw new BindingException("Unknown execution method for: " + this.commandName); } if (this.returnsVoid && this.resultHandlerIndex != null) { this.executeWithResultHandler(args); } else if (this.returnsList) { result = this.executeForList(args); } else if (this.returnsMap) { result = this.executeForMap(args); } else { param = this.getParam(args); result = this.sqlSession.selectOne(this.commandName, param); } } return result; }
getParam方法:
Mybatis的缓存:
Mybatis提供查询缓存,用于减轻数据库压力,提高数据库性能,Mybatis提供一级缓存,二级缓存
一级缓存(粒度小):SqlSession级别的缓存,在操作数据库时需要构造SqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据,不同的SqlSession之间的缓
存数据区域是互相不影响的。
当第一次发起查询请求时,先去缓存中查找有没有符合的信息,如果没有,就从数据库中去查,然后将结果信息存储到一级缓存中
如果SqlSession执行了Commit操作(插入、删除、更新)等,将清空SqlSession中的一级缓存,为了让缓存中的信息是最新信息,避免脏读,Mybatis默认是支持一级缓存的,
关掉一级缓存的话需要在配置文件中配置。
SqlSession关闭,一级缓存就清空
应用:将Mybatis和Spring整合开发,事务控制是在service中,开始执行时,开启事务,创建SqlSession对象。
在service方法内第一次调用,第二次调用将从一级缓存中取数据,方法结束,SqlSession关闭
如果执行了两次service方法调用查询相同的信息,不走一级缓存,因为service方法结束,SqlSession就关闭了,一级缓存也随之清空
二级缓存(粒度大):Mapper级别的缓存,多个SqlSession去操作同一个mapper sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
多个SqlSession共享一个Mapper的二级缓存区域,按照namespace区分,每个mapper有都按自己的namespace区分的缓存区域。二级缓存默认是也是开启的
开启二级缓存:
1):在mybatis-config.xml配置文件的setting标签中设置二级缓存的开关
2):在每个具体的mapper.xml文件中开启二级缓存
3):调用pojo类实现序列化接口,因为为了将缓存数据取出执行反序列操作,因为二级缓存存储介质多种多样,不一定在内存
禁用缓存:
在每个select标签中设置useCache="false",如果想要针对每次查询都需要最新的数据,则需要设置禁用二级缓存
Mybatis和Spring整合:
1)、需要Spring通过单例方式管理SqlSessionFactory,注入org.mybatis.spring.SqlSessionFactoryBean指定mybatis配置文件地址、dataSource、mapper.xml、别名等
2)、Spring和Mybatis整合生成代理对象,使用SqlSessionFactory创建Session(整合自动完成),提供SqlSessionTemplate
3)、持久层的mapper都需要由Spring进行管理