本章主要介绍的是MyBatis的基础应用和源码涉及的相关等,主要包含的内容有MyBatis的简介、反射、动态代理(包含JDK代理和cglib代理)、MyBatis使用和代码生成器等。
1.1 MyBatis简介
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
关于MyBatis相关的信息,大家可以参考如下:
MyBatis官网: http://www.mybatis.org/
MyBatis文档: http://www.mybatis.org/mybati...
MyBatis源码地址: https://github.com/mybatis/my...
1.2 反射
1.2.1 反射简介
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
JAVA反射(放射)机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。但是JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
1.2.2 反射的应用场景
从实用的角度看,笔者认为有如下几个场景?大家可以做一个思考。
(1) 逆向代码,例如反编译;
(2) 与注解结合的框架,现在有不少java框架都采用注解的方式,例如Spring既可以通过注解管理事务又可以通过XML方式管理事务;
(3) 插件系统, 程序设计了加载新JAR的功能,陌生JAR文件在程序运行时加入,被编译成class文件,通过反射机制加载进内存;
(4) 配置文件中的休眠class类, 一个程序有许许多多功能,不可能相对应的class文件一次性全部加载进内存,只能是加载进维持程序运行的基本class,其余功能只有等用的时候,采用触发机制,通过xml配置文件获取类名等信息,然后通过反射机制加载相关class进内存。
1.2.3 反射实例
代码如下:
package cn.reflect;
import java.lang.reflect.Method;
public class ReflectService {
/**
* 测试方法
* @param name
*/
public void testReflect(String name) {
System.out.println("hello:"+name);
}
/**
* 测试入口
* @throws Exception
*/
public static void main(String[] args) throws Exception {
/**
* 通过反射创建ReflectService对象
*/
Object service = Class.forName(ReflectService.class.getName()).newInstance();
/**
* 获取服务方法
*/
Method method = service.getClass().getMethod("testReflect", String.class);
method.invoke(service, "张三");
}
}
1.2.4反射的优缺点
反射在给我们带来极大的便利的同时,也带给我们潜在的危害。这也就是有利也有弊。
反射的优点:
反射提高了程序的灵活性和扩展性,降低耦合性,提高自适应能力。它允许程序创建和控制任何类的对象,无需提前硬编码目标类。
反射的缺点:
(1)性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用。
(2)使用反射会模糊程序内内部逻辑:程序员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。
1.3 动态代理
1.3.1 动态代理简介
动态代理有两种方式,一种是JDK的代理,另外一种是cglib代理。
它们之间的区别是,JDK是代理接口,cglib代理类。
换言之,一个是接口方式,一个是类的方式,类的方式通常表现为继承。
1.3.2 JDK代理应用实例
步骤如下:
(1)编写接口类
HelloService.java
package cn.reflect;
public interface HelloService {
public void sayHello(String name);
}
(2)编写实现类
HelloServiceImpl.java
package cn.reflect;
public class HelloServiceImpl implements HelloService {
public void sayHello(String name) {
// TODO Auto-generated method stub
System.err.println("hello:"+name);
}
}
(3)编写代理类
HelloServiceProxy.java
package cn.reflect;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class HelloServiceProxy implements InvocationHandler {
/**
* 真实服务对象
*/
private Object target;
public Object bind(Object target){
this.target=target;
/**
* 取得代理对象
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);//jdk代理对象需要提供接口
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("我是JDK动态代理对象");
Object result = null;
/**
* 反射方法调用前
*/
System.out.println("我准备说hello");
/**
* 执行方法,相当于调用HelloServiceImpl中的sayHello方法
*/
result = method.invoke(target, args);
/**
* 反射方法后调用
*/
System.out.println("我说过hello了");
return result;
}
}
(4)编写运行测试类
HelloServiceMain.java
package cn.reflect;
public class HelloServiceMain {
public static void main(String[] args) {
HelloServiceProxy helloHandle = new HelloServiceProxy();
HelloService proxy = (HelloService) helloHandle.bind(new HelloServiceImpl());
proxy.sayHello("张三");
}
}
1.3.3 cglib代理应用实例
JDK提供的动态代理存在缺陷,必须提供接口才能使用,没有接口就不能使用,为了克服这个缺陷,我们可以采用cglib代理,它是一种流行的动态代理。
步骤如下:
(1) 导入maven依赖
cglib
cglib
3.2.4
(2) 编写运行测试类
HelloServiceCgLib.java
package cn.reflect;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class HelloServiceCgLib implements MethodInterceptor{
private Object target;
/**
* 创建代理对象
*/
public Object getInstance(Object target) {
this.target=target;
Enhancer enHancer = new Enhancer();
enHancer.setSuperclass(this.target.getClass());
enHancer.setCallback(this);
return enHancer.create();
}
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable {
// TODO Auto-generated method stub
System.out.println("我是cglib代理对象");
Object returnObj = proxy.invoke(obj, arg);
/**
* 反射方法前调用
*/
System.out.println("我准备说hello");
/**
* 反射方法后调用
*/
System.out.println("我说过hello了");
return returnObj;
}
}
1.4 MyBatis使用
1.4.1 导入依赖
由于采用的是maven,只需引入maven对应的依赖即可,通常情况下,只要maven依赖导入准确,一般情况下很少会出现因为依赖冲突的问题导致项目运行失败。
pom.xml
4.0.0
cn.youcong.mybatis
mybatis
0.0.1-SNAPSHOT
org.mybatis
mybatis
3.4.6
mysql
mysql-connector-java
5.1.38
junit
junit
4.12
test
1.4.2 准备SQL脚本
CREATE TABLE `user` (
`user_id` int(8) NOT NULL AUTO_INCREMENT COMMENT '用户主键',
`login_code` varchar(20) NOT NULL COMMENT '用户编码(登录账户) 手机号 邮箱号',
`user_name` varchar(20) NOT NULL COMMENT '用户名',
`password` varchar(40) NOT NULL COMMENT '密码',
`sex` int(2) NOT NULL COMMENT '性别',
`identity_card` varchar(20) DEFAULT NULL COMMENT '身份证',
`create_time` datetime NOT NULL COMMENT '创建时间',
`create_by` varchar(10) NOT NULL COMMENT '创建人',
`update_time` datetime NOT NULL COMMENT '更新时间',
`update_by` varchar(10) NOT NULL COMMENT '更新人',
`status` int(2) NOT NULL DEFAULT '0' COMMENT '状态:0注册新用户 1邮件认证用户 2管理员 3黑名单',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
1.4.3 编写实体
package com.blog.entity;
import java.io.Serializable;
import java.util.Date;
import java.io.Serializable;
public class User{
private static final long serialVersionUID = 1L;
/**
* 用户主键
*/
private Integer userId;
/**
* 用户编码(登录账户) 手机号 邮箱号
*/
private String loginCode;
/**
* 用户名
*/
private String userName;
/**
* 密码
*/
private String password;
/**
* 性别
*/
private Integer sex;
/**
* 身份证
*/
private String identityCard;
/**
* 创建时间
*/
private Date createTime;
/**
* 创建人
*/
private String createBy;
/**
* 更新时间
*/
private Date updateTime;
/**
* 更新人
*/
private String updateBy;
/**
* 状态:0注册新用户 1邮件认证用户 2管理员 3黑名单
*/
private Integer status;
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getLoginCode() {
return loginCode;
}
public void setLoginCode(String loginCode) {
this.loginCode = loginCode;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public String getIdentityCard() {
return identityCard;
}
public void setIdentityCard(String identityCard) {
this.identityCard = identityCard;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
1.4.4 编写数据访问层及其对应的XML文件
package com.blog.dao;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import com.blog.entity.User;
public interface UserDao {
@Select("select * from `user` where user_id=#{userId}")
@Results(@Result(property="userName",column="user_name"))
User selectOne(int userId);
}
@Select是MyBatis的注解写法,当字段名和属性名一致时,可以直接@Select不需要@Results,@Results的作用就是因为实体属性与字段不一致,为了获取查询结果,必须要这样。
当然了你也可以不写注解,直接在对应的UserDao.xml写,如果是UserDao.xml里面写的话,就会变成这样
package com.blog.dao;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import com.blog.entity.User;
public interface UserDao {
User selectOne(int userId);
}
需要在UserDao.xml中编写
user_id, login_code, user_name, password, sex, identity_card, create_time, create_by, update_time, update_by, status
除了
user_id, login_code, user_name, password, sex, identity_card, create_time, create_by, update_time, update_by, status
MyBatis常用的两种写法,XML方式和注解方式,但是无论你采用哪种,对应的XML文件必须要存在。
其实
前提必须要在mybatis-config.xml配置
但是有人觉得,如果我的类有很多,岂不是要配置很多,太麻烦了,别怕,MyBatis已经为你想到了
这样配置就解决了重复配置的麻烦
其实除了这样,如果你想标新立异点,还可以再实体最上面加上@Alias注解
这就是MyBatis的别名三种方式/策略。
1.4.5 编写mybatis-config.xml配置文件
mybatis-config.xml这个配置没多大用,坦白说。
因为后期与Spring整合由Spring来管理数据库连接池对象。
不过还是要稍微讲讲,以让读者达到开卷有益的目的或者是让没有学过的或者已经学过但是不知道的涨涨见识。
1.4.6 编写工具类
package com.blog.config;
import java.io.IOException;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class SqlSessionFactoryUtils {
private static SqlSessionFactory sqlSessionFactory = null;
public static SqlSessionFactory getSqlSessionFactory() {
InputStream is = null;
if(sqlSessionFactory==null) {
String resource = "mybatis/mybatis-config.xml";
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(resource));
return sqlSessionFactory;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return sqlSessionFactory;
}
}
说到上述代码,顺便谈谈MyBatis的核心组件。它的核心组件主要有这么几个,如下所示:
SqlSessionFactoryBuilder:它会根据配置信息或者代码来生成SqlSessionFactory;
SqlSessionFactory:依赖工厂生成SqlSession;
SqlSession:是一个即可发送SQL执行返回结果,也可以获取Mapper接口;
Sql Mapper:它是MyBatis的新设计组件,它是由一个Java接口和XML文件(或注解)构建,需要给出对应的SQL和映射规则。它负责发送SQL去执行并返回结果。
用一张图来表示它们之间的关系,如图:
它们的生命周期在此稍微说一下:
(1)SqlSessionFacotoryBuilder
SqlSessionFacotoryBuilder是利用XML和Java代码获得资源来构建SqlSessionFactory的,通过它可以构建多个SqlSessionFactory,它的作用就是一个构建器,一旦我们构建了SqlSessionFactory,它的作用就完结,它就失去存在的意义。这时我们应该毫不犹豫的废弃它,将它回收。所以它的生命周期只存在方法局部,它的作用就是生成SqlSessionFactory。
(2)SqlSessionFactory的作用是创建SqlSession,而SqlSession就是一个会话,相当于JDBC中的Connection对象。每次应用程序需要访问数据库,我们就要通过SqlSessionFactory创建SqlSession,所以SqlSessionFactory应该在MyBatis应用的整个生命周期中。而如果我们多次创建同一个数据库的SqlSessionFactory,则每次创建SqlSessionFactory会打开更多的数据库连接资源,那么连接资源就很快会被耗尽。因此SqlSessionFactory的责任是唯一的,它的责任就是创建SqlSession,所以我们果断采用单例模式。如果采用多例,那么它对数据库连接的消耗是很大的,不利于我们统一管理。所以正确的做法是使得每个数据库只对应一个SqlSessionFactory,管理好数据库资源分配,避免过多的Connection被消耗。
(3)SqlSession
SqlSession是一个会话,相当于JDBC的一个Connection对象,它的生命周期应该是在请求数据库处理事务的过程中。它是一个线程不安全的对象,在涉及多线程的时候我们需要特别当心,操作数据库需要注意其隔离级别,数据库锁等高级特性。此外,每次创建的SqlSession都必须及时关闭它,它长期存在就会使数据库连接池的活动资源减少,对系统性能的影响很大。
(4)Mapper
Mapper是一个接口,而没有任何实现类,它的作用是发送SQL,然后返回我们需要的结果,或者执行SQL从而修改数据库的数据,因此它应该在一个SqlSession事务方法之内,是一个方法级别的东西。它就如同JDBC中的一条SQL语句的执行,它的最大范围和SqlSession是相同的。尽管我们想一直保存着Mapper,但是你会发现它很难控制,所以尽量在一个SqlSession事务的方法中使用它们,然后废弃掉。
MyBatis组件生命周期,如图:
1.4.7 测试
package mybatis;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import com.blog.config.SqlSessionFactoryUtils;
import com.blog.dao.UserDao;
import com.blog.entity.User;
public class MyBatisTest {
@Test
public void testName() throws Exception {
SqlSession sqlSession = null;
sqlSession = SqlSessionFactoryUtils.getSqlSessionFactory().openSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.selectOne(1);
System.out.println(user.getUserName());
}
}
1.5 MyBatis代码生成器
MyBatis代码生成器,又称逆向工程。
关于逆向工程的优点和缺点,我觉得我还是要说说。
逆向工程的优点是:自动化生成实体类和对应的增删改查,效率相对于之前个人开发时一个个写增删改查要高的多
逆向工程的缺点是:xml中的sql语句加入了mybatis自身的动态sql和一大堆判断等,对于对动态sql不是十分熟练的人而言,以后再功能扩展上会很困难。
================
MyBatis代码生成器我分为两个,一个是动态web工程,另一个是现在比较流行的maven工程,本质上都是一样,只是项目管理jar包的方式不一样。
1.5.1动态web工程
(1)导包
(2)新建generator.xml文件,进行配置
(3)测试
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;
public class GeneratorSqlmap {
public void generator() throws Exception{
List warnings = new ArrayList();
boolean overwrite = true;
//指定 逆向工程配置文件
File configFile = new File("src/generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
public static void main(String[] args) throws Exception {
try {
GeneratorSqlmap generatorSqlmap = new GeneratorSqlmap();
generatorSqlmap.generator();
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果在控制台打印如下,表示成功
1.5.2 maven工程
(1)导入maven依赖
4.0.0
cn.mybatis.generator
mybatis-generator
0.0.1-SNAPSHOT
UTF-8
mysql
mysql-connector-java
5.1.35
org.mybatis.generator
mybatis-generator-core
1.3.2
org.apache.maven.plugins
maven-compiler-plugin
1.8
3.3
org.mybatis.generator
mybatis-generator-maven-plugin
1.3.2
mysql
mysql-connector-java
5.1.35
src/main/resources/generatorConfig.xml
true
(2)在src/main/resource目录下新建配置文件generatorConfig.xml
(3)右击进入run as 点击maven build 如下图输入:mybatis-generator:generate
运行结果如下,表示成功
注意:运行成功后,直接是看不到生成的代码,还需要刷新一下(refresh)。
1.6 小结
通过上面的内容,我相信读者对于MyBatis已经有了大致的了解,通过相关的代码示例,我相信读者应该知道怎么使用MyBatis了。当然了,这仅仅只是MyBatis的冰山一角,MyBatis的特性还有很多。不过我之所以讲MyBatis也是为了后续读者更好理解MyBatis-plus。
本文主要参考我个人的博客园中的MyBatis系列相关的,可以说这篇文章是之前MyBatis相关教程不太完善的补充。希望能够帮助到大家。
最后,如需转载本文,请附源地址,谢谢合作。