1.mybatis概述
ORM:对象关系映射
O:object,java中的类对象
R:关系型数据库
M:mapper映射
mybatis是一个半自动的ORM框架,因为sql语句是需要自己编写的
Hibernate是一个全自动的ORM框架,sql语句可以自动生成
mybatis框架的特点
支持定制化sql,存储过程,基本映射以及高级映射
避免了几乎所有的JDBC代码中手动设置参数以及获取结果
支持XML开发,也支持注解开发(为了保证sql的灵活,一般使用xml)
将接口和java中的pojo映射成数据库中的记录
体积小,一个mybatis jar包 和一个连接jar包,两个配置文件(核心配置和jdbc)
完全做到sql解耦合
支持动态sql
2.开发第一个mybatis程序
开发步骤
第一步:打包方式jar
第二步:引入依赖 - mybatis依赖 - mysql驱动依赖
第三步:编写mybatis核心配置文件:mybatis-config.xml 注意: 第一:这个文件名不是必须叫做mybatis-config.xml,可以用其他的名字。只是大家都采用这个名字。 第二:这个文件存放的位置也不是固定的,可以随意,但一般情况下,会放到类的根路径下。 mybatis-config.xml文件中的配置信息不理解没关系,先把连接数据库的信息修改以下即可。 其他的别动。
第四步:编写XxxxMapper.xml文件 在这个配置文件当中编写SQL语句。 这个文件名也不是固定的,放的位置也不是固定,我们这里给它起个名字,叫做:CarMapper.xml 把它暂时放到类的根路径下。
第五步:在mybatis-config.xml文件中指定XxxxMapper.xml文件的路径: <mapper resource="CarMapper.xml"/> 注意:resource属性会自动从类的根路径下开始查找资源。
第六步:编写MyBatis程序。(使用mybatis的类库,编写mybatis程序,连接数据库,做增删改查就行了。)
在MyBatis当中,负责执行SQL语句的那个对象叫做什么呢? SqlSession SqlSession是专门用来执行SQL语句的,是一个Java程序和数据库之间的一次会话。
要想获取SqlSession对象,需要先获取SqlSessionFactory对象,通过SqlSessionFactory工厂来生产SqlSession对象。
怎么获取SqlSessionFactory对象呢? 需要首先获取SqlSessionFactoryBuilder对象。 通过SqlSessionFactoryBuilder对象的build方法,来获取一个SqlSessionFactory对象。 mybatis的核心对象包括:
SqlSessionFactoryBuilder SqlSessionFactory SqlSession SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession
可以使用工具类来封装这一过程
使用静态代码块的好处是,只需要在程序开始的时候获取一下就行了,不需要过多的获取
public class SqlSessionUtil {
// 工具类的构造方法一般都是私有化的。
// 工具类中所有的方法都是静态的,直接采用类名即可调用。不需要new对象。
// 为了防止new对象,构造方法私有化。
private SqlSessionUtil(){}
private static SqlSessionFactory sqlSessionFactory;
// 类加载时执行
// SqlSessionUtil工具类在进行第一次加载的时候,解析mybatis-config.xml文件。创建SqlSessionFactory对象。
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/*public static SqlSession openSession(){
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// SqlSessionFactory对象:一个SqlSessionFactory对应一个environment,一个environment通常是一个数据库。
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}*/
/**
* 获取会话对象。
* @return 会话对象
*/
public static SqlSession openSession(){
return sqlSessionFactory.openSession();
}
}
关于第一个程序的小细节 * mybatis中sql语句的结尾";"可以省略。 Resources.getResourceAsStream 小技巧:以后凡是遇到resource这个单词,大部分情况下,这种加载资源的方式就是从类的根路径下开始加载。(开始查找) 优点:采用这种方式,从类路径当中加载资源,项目的移植性很强。项目从windows移植到linux,代码不需要修改,因为这个资源文件一直都在类路径当中。
InputStream is = new FileInputStream("d:\\mybatis-config.xml"); 采用这种方式也可以。 缺点:可移植性太差,程序不够健壮。可能会移植到其他的操作系统当中。导致以上路径无效,还需要修改java代码中的路径。这样违背了OCP原则。
已经验证了: mybatis核心配置文件的名字,不一定是:mybatis-config.xml。可以是其它名字。 mybatis核心配置文件存放的路径,也不一定是在类的根路径下。可以放到其它位置。但为了项目的移植性,健壮性,最好将这个配置文件放到类路径下面。
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
ClassLoader.getSystemClassLoader() 获取系统的类加载器。 系统类加载器有一个方法叫做:getResourceAsStream 它就是从类路径当中加载资源的。
通过源代码分析发现上面的就等于这个: InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
CarMapper.xml文件的名字是固定的吗?CarMapper.xml文件的路径是固定的吗? 都不是固定的。 <mapper resource="CarMapper.xml"/> resource属性:这种方式是从类路径当中加载资源。 <mapper url="file:///d:/CarMapper.xml"/> url属性:这种方式是从绝对路径当中加载资源。
3.关于mybatis事务管理机制
在mybatis.xml文件中,可以通过配置<transactionManager type="JDBC"/>管理事务
type属性的值包括两个:JDBC(jdbc) MANAGED(managed)
mybatis中提供了上述两种事务管理机制
jdbc事务管理器:
mybatis框架自己管理事务,采用自己原生的jdbc代码去管理事务:
conn.setAutoCommit(false); 开启事务。
业务处理
conn.commit(); 手动提交事务
使用jdbc管理事务的话,低层创建事务管理器对象:jdbctransaction对象
如果通过工厂方法开启SqlSession会话的时候
SqlSession sqlSession = sqlSessionFactory.openSession(true);
表明不开启事务,这种方式在低层的源码中不会开启事务,在一个判断条件中跳过了 conn.setAutoCommit(false);语句。执行一条语句就会直接提交。
MANAGED事务管理器:
mybatis不再负责事务管理器,事务管理器交给其他容器来负责,例如spring
如果这个时候只有mybatis的情况下,就会没人管理事务,相当于不开启的状态
4.mybatis的日志组件
mybatis常见的日志组件
SLF4J(沙拉风):沙拉风是一个日志标准,其中有一个框架叫做logback,它实现了沙拉风规范。
LOG4J
LOG4J2
STDOUT_LOGGING ....
注意:log4j log4j2 logback都是同一个作者开发的。
其中STDOUT_LOGGING是标准日志,mybatis已经实现了这种日志
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
另外,mybatis的配置文件中有顺序存在,必须注意标签的顺序。
集成logback日志框架
logback日志框架实现了slf4j标准。(沙拉风:日志门面。日志标准。)
第一步:引入logback的依赖。
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
第二步:引入logback所必须的xml配置文件。 这个配置文件的名字必须叫做:logback.xml或者logback-test.xml,不能是其它的名字。 这个配置文件必须放到类的根路径下。不能是其他位置。 主要配置日志输出相关的级别以及日志具体的格式。
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>[%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!--mybatis log configure-->
<logger name="com.apache.ibatis" level="TRACE"/>
<logger name="java.sql.Connection" level="DEBUG"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
<!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR -->
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
mybatis配置文件详解
1.首先是properties标签
这个标签是一个map集合类,都是String类型,可以用这种方式直接配置数据源,然后再下面环境配置的数据源的位置使用${jdbc.driver}的方式来引用value的值
<properties>
<!-- 这是其中的一个属性-->
<property name="属性名" value="属性值"/>
<property name="jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbc.url" value="jdbc:mysql://localhost:3306/powernode"/>
<property name="jdbc.username" value="root"/>
<property name="jdbc.password" value="root"/>
</properties>
另一种方式是使用
<!--resource,一定是从类路径下开始查找资源-->
<properties resource="jdbc.properties" />
<!--从绝对路径当中加载资源。绝对路径怎么写?file:///路径-->
<!--<properties url="file:///d:/jdbc.properties" />-->
2.然后是<typeAliases>标签,用于批量注册pojo类的名称,在mapper映射文件中可以直接使用类名的驼峰命名来代替全路径名称
<typeAliases>
<package name="com.mybatis.pojo"/>
</typeAliases>
3.settings标签 :用于打印日志,有好几种日志打印方式
<settings>
<setting name="logImpl" value="STDOUT_LOGING"/>
</settings>
4. environments标签
用于mybatis的环境使用,一般定义数据源和事务。可以有多个环境的定义,只需要更改default的值就可以更改环境的使用。
<!--default表示默认使用的环境。-->
<!--默认环境什么意思?当你使用mybatis创建SqlSessionFactory对象的时候,没有指定环境的话,默认使用哪个环境。-->
<environments default="powernodeDB">
<!--其中的一个环境。连接的数据库是powernode-->
<!--一般一个数据库会对应一个SqlSessionFactory对象。-->
<!--一个环境environment会对应一个SqlSessionFactory对象-->
<environment id="powernodeDB">
<!--
transactionManager标签:
1.作用:配置事务管理器。指定mybatis具体使用什么方式去管理事务。
2.type属性有两个值:
第一个:JDBC: 使用原生的JDBC代码来管理事务。
conn.setAutoCommit(false);
....
conn.commit();
第二个:MANAGED:mybatis不再负责事务的管理,将事务管理交给其它的JEE(JavaEE)容器来管理。例如:spring
3. 大小写无所谓。不缺分大小写。但是不能写其他值。只能是二选一:
jdbc、managed
4. 在mybatis中提供了一个事务管理器接口:Transaction
该接口下有两个实现类:
JdbcTransaction
ManagedTransaction
如果type="JDBC",那么底层会实例化JdbcTransaction对象。
如果type="MANAGED",那么底层会实例化ManagedTransaction
-->
<transactionManager type="JDBC"/>
<!--
dataSource配置:
1.dataSource被称为数据源。
2.dataSource作用是什么?为程序提供Connection对象。(但凡是给程序提供Connection对象的,都叫做数据源。)
3.数据源实际上是一套规范。JDK中有这套规范:javax.sql.DataSource(这个数据源的规范,这套接口实际上是JDK规定的。)
4.我们自己也可以编写数据源组件,只要实现javax.sql.DataSource接口就行了。实现接口当中所有的方法。这样就有了自己的数据源。
比如你可以写一个属于自己的数据库连接池(数据库连接池是提供连接对象的,所以数据库连接池就是一个数据源)。
5.常见的数据源组件有哪些呢【常见的数据库连接池有哪些呢】?
阿里巴巴的德鲁伊连接池:druid
c3p0
dbcp
....
6. type属性用来指定数据源的类型,就是指定具体使用什么方式来获取Connection对象:
type属性有三个值:必须是三选一。
type="[UNPOOLED|POOLED|JNDI]"
UNPOOLED:不使用数据库连接池技术。每一次请求过来之后,都是创建新的Connection对象。
POOLED:使用mybatis自己实现的数据库连接池。
JNDI:集成其它第三方的数据库连接池。
JNDI是一套规范。谁实现了这套规范呢?大部分的web容器都实现了JNDI规范:
例如:Tomcat、Jetty、WebLogic、WebSphere,这些服务器(容器)都实现了JNDI规范。
JNDI是:java命名目录接口。Tomcat服务器实现了这个规范。
-->
<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}"/>
<!--提醒:正常使用连接池的话,池中有很多参数是需要设置的。设置好参数,可以让连接池发挥的更好。事半功倍的效果。-->
<!--具体连接池当中的参数如何配置呢?需要反复的根据当前业务情况进行测试。-->
<!--poolMaximumActiveConnections:连接池当中最多的正在使用的连接对象的数量上限。最多有多少个连接可以活动。默认值10-->
<property name="poolMaximumActiveConnections" value="10"/>
<!--每隔2秒打印日志,并且尝试获取连接对象-->
<property name="poolTimeToWait" value="2000"/>
<!--强行让某个连接空闲,超时时间的设置-->
<property name="poolMaximumCheckoutTime" value="10000"/>
<!--最多的空闲数量-->
<property name="poolMaximumIdleConnections" value="5"/>
</dataSource>
</environment>
<!--这是mybatis的另一个环境,也就是连接的数据库是另一个数据库mybatis-->
<environment id="mybatisDB">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
5.mappers标签
用于注册映射文件的位置,可以使用单独注册的mapper方式或者批量的包注册
<mappers>
<mapper resource="CarMapper.xml"/>
<!-- <package name="com.mybatis.dao"/>-->
</mappers>
5.使用javassist来动态生成类并实现方法
首先这个包在mybatis下import org.apache.ibatis.javassist.*;
首先有一个接口,里面有一些方法
@Test
public void testDaoImpl1() throws CannotCompileException, IllegalAccessException, InstantiationException {
// 获取类池
ClassPool pool=ClassPool.getDefault();
// 制造类
CtClass ctClass = pool.makeClass("com.bank.dao.impl.0AccountDaoImpl");
// 制造接口
CtClass makeInterface = pool.makeInterface("com.bank.dao.AccountDao");
// 添加接口到类中
ctClass.addInterface(makeInterface);
// 实现接口中的方法
CtMethod method = CtMethod.make("public void delete(){ System.out.println(\"delete\");}", ctClass);
// 将方法添加到类中
ctClass.addMethod(method);
// 在内存中生成类,同时加载到jvm中
Class<?> aClass = ctClass.toClass();
// 通过反射机制创建对象
AccountDao accountDao = (AccountDao) aClass.newInstance();
accountDao.delete();
}
}
这样就可以直接在内存中生成这个实现类的字节码文件,然后通过反射机制来创建接口对象来调用方法。
这是一个简单的实现类生成方法,mybatis可以帮你创建持久层接口的实现类,使用的是类似于这种创建在内存中的方式
**
* 工具类:可以动态的生成DAO的实现类。(或者说可以动态生成DAO的代理类)
* 注意注意注意注意注意!!!!!!:
* 凡是使用GenerateDaoProxy的,SQLMapper.xml映射文件中namespace必须是dao接口的全名,id必须是dao接口中的方法名。
* @author 动力节点
* @version 1.0
* @since 1.0
*/
public class GenerateDaoProxy { // GenerateDaoProxy是mybatis框架的开发者写的。
/**
* 生成dao接口实现类,并且将实现类的对象创建出来并返回。
* @param daoInterface dao接口
* @return dao接口实现类的实例化对象。
*/
public static Object generate(SqlSession sqlSession, Class daoInterface){
// 类池
ClassPool pool = ClassPool.getDefault();
// 制造类(com.powernode.bank.dao.AccountDao --> com.powernode.bank.dao.AccountDaoProxy)
CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy"); // 实际本质上就是在内存中动态生成一个代理类。
// 制造接口
CtClass ctInterface = pool.makeInterface(daoInterface.getName());
// 实现接口
ctClass.addInterface(ctInterface);
// 实现接口中所有的方法
Method[] methods = daoInterface.getDeclaredMethods();
Arrays.stream(methods).forEach(method -> {
// method是接口中的抽象方法
// 将method这个抽象方法进行实现
try {
// Account selectByActno(String actno);
// public Account selectByActno(String actno){ 代码; }
StringBuilder methodCode = new StringBuilder();
methodCode.append("public ");
methodCode.append(method.getReturnType().getName());
methodCode.append(" ");
methodCode.append(method.getName());
methodCode.append("(");
// 需要方法的形式参数列表
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
methodCode.append(parameterType.getName());
methodCode.append(" ");
methodCode.append("arg" + i);
if(i != parameterTypes.length - 1){
methodCode.append(",");
}
}
methodCode.append(")");
methodCode.append("{");
// 需要方法体当中的代码
methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");
// 需要知道是什么类型的sql语句
// sql语句的id是框架使用者提供的,具有多变性。对于我框架的开发人员来说。我不知道。
// 既然我框架开发者不知道sqlId,怎么办呢?mybatis框架的开发者于是就出台了一个规定:凡是使用GenerateDaoProxy机制的。
// sqlId都不能随便写。namespace必须是dao接口的全限定名称。id必须是dao接口中方法名。
String sqlId = daoInterface.getName() + "." + method.getName();
SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
if (sqlCommandType == SqlCommandType.INSERT) {
}
if (sqlCommandType == SqlCommandType.DELETE) {
}
if (sqlCommandType == SqlCommandType.UPDATE) {
methodCode.append("return sqlSession.update(\""+sqlId+"\", arg0);");
}
if (sqlCommandType == SqlCommandType.SELECT) {
String returnType = method.getReturnType().getName();
methodCode.append("return ("+returnType+")sqlSession.selectOne(\""+sqlId+"\", arg0);");
}
methodCode.append("}");
CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
ctClass.addMethod(ctMethod);
} catch (Exception e) {
e.printStackTrace();
}
});
// 创建对象
Object obj = null;
try {
Class<?> clazz = ctClass.toClass();
obj = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
例如上述这种代理方式,只需要获取接口的字节码文,然后就可以通过反射机制获取接口中的方法信息。然后可以构建出接口的实现类。
接口中的方法具体的内容则由mapper.xml文件写,解析这个文件的时候需要根据id来寻找对应的信息,所以在使用mybatis的时候必须把namespace的值设置为接口的全路径名称,sql语句的id设置为方法的名称。通过这种方法来为你在内存中创建出一个实现类,本质山还是调用sqlsession的操作数据库方法。