MyBatis的简单实现
Maper的构建过程
SqlSession基本构建的代码为:
String resource = "mybatis-config.xml";//设置mybatis配置文件的资源路径(类路径下)
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);//获取配置文件的输入流
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//通过SqlSessionFactoryBuilder的build方法,将输入流中的信息进行配置,返回一个SqlSessionFactory对象
SqlSession session = sqlSessionFactory.openSession();
- 获取SqlSessionFactory:
关键就在于mybatis使用SqlSessionFacoty来获取SqlSession对象,SqlSessionFactory是一个接口,默认使用的实现是DefaultSqlSessionFactory,在创建DefaultSqlSessionFactory时将xml配置文件解析后封装的Configuration对象传入,就得到了该配置文件所对应的SqlSessionFactory - 获取SqlSession:
- 获取Mapper:
在获取Mapper时传入参数为编写Dao接口的class,而mybatis能够返回一个类对象,显然是使用了动态代理
简单实现
MyBatis内部逻辑比这要复杂得多,这里简单实现mybatis工作流程
配置文件Dom类
- MyConfiguration
/**
* mybatis-config配置文件对应的dom对象
* @author lenovo
*
*/
public class MyConfiguration {
//数据库连接信息
private String url;
private String driver;
private String username;
private String password;
//映射文件信息,键使用namespace+id保证唯一性,值使用一个MyMappedStatement对象
private Map<String,MyMappedStatement> mappers = new HashMap<>();
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
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 Map<String,MyMappedStatement> getMappers() {
return mappers;
}
public void setMappers(Map<String,MyMappedStatement> mappers) {
this.mappers = mappers;
}
@Override
public String toString() {
return "MyConfiguration [url=" + url + ", driver=" + driver + ", username=" + username + ", password="
+ password + ", mappers=" + mappers + "]";
}
}
- MyMappedStatement
/**
* mapper映射文件dom对象
* @author lenovo
*
*/
public class MyMappedStatement {
private String namespace;
private String id;
private String resultType;
private String sql;
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
}
SqlSessionFactory的构建
- MySqlSessionFactory
/**
* sqlsession工厂,用于获取SqlSession对象
* @author shaofan
*
*/
public interface MySqlSessionFactory {
MySqlSession openSession();
}
- MyDefaultSqlSessionFactory,SqlSessionFactory的默认实现,这里进行mybatis配置文件的读取
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
* SqlSessionFactory的默认实现
*
* @author shaofan
*
*/
public class MyDefaultSqlSessionFactory implements MySqlSessionFactory {
private final MyConfiguration myConfiguration = new MyConfiguration();;
private final SAXReader reader = new SAXReader();
public MyDefaultSqlSessionFactory(String xmlPath) {
Document document = null;
try {
//读取类路径下的配置文件
document = reader.read(this.getClass().getResource(xmlPath));
} catch (DocumentException e) {
e.printStackTrace();
}
// 加载数据库配置文件
loadDBInfo(document);
// 加载映射文件
loadMapperInfo(document);
}
private void loadDBInfo(Document document) {
Element element = document.getRootElement();
for (Element e : element.element("dataSource").elements("property")) {
try {
//通过标签name属性找到对应的配置字段并调用set方法将配置存入对象中
Method method = MyConfiguration.class.getDeclaredMethod("set"
+e.attributeValue("name").substring(0, 1).toUpperCase()
+e.attributeValue("name").substring(1),String.class);
method.invoke(myConfiguration, e.attributeValue("value"));
} catch (NoSuchMethodException e1) {
e1.printStackTrace();
} catch (SecurityException e1) {
e1.printStackTrace();
} catch (IllegalAccessException e1) {
e1.printStackTrace();
} catch (IllegalArgumentException e1) {
e1.printStackTrace();
} catch (InvocationTargetException e1) {
e1.printStackTrace();
}
}
}
private void loadMapperInfo(Document document) {
Element root = document.getRootElement();
//遍历mapper标签
for(Element e:root.element("mappers").elements("mapper")) {
//通过配置的映射文件路径加载映射文件
URL mapperPath = this.getClass().getResource(e.attributeValue("resource"));
Document mapperDocument = null;
try {
mapperDocument = reader.read(mapperPath);
} catch (DocumentException e1) {
e1.printStackTrace();
}
Element mapperRoot = mapperDocument.getRootElement();
MyMappedStatement myMappedStatement = new MyMappedStatement();
myMappedStatement.setNamespace(mapperRoot.attributeValue("namespace"));
for(Element ele:mapperRoot.elements()) {
myMappedStatement.setId(ele.attributeValue("id"));
myMappedStatement.setResultType(ele.attributeValue("resultType"));
myMappedStatement.setSql(ele.getData().toString());
//当出现重复的sql配置时抛出异常
if(myConfiguration.getMappers().containsKey(myMappedStatement.getNamespace()+"."+myMappedStatement.getId())) {
throw new RuntimeException("重复的方法");
}
myConfiguration.getMappers().put
(myMappedStatement.getNamespace()+"."+myMappedStatement.getId(),
myMappedStatement);
}
}
System.out.println(myConfiguration);
}
@Override
public MySqlSession openSession() {
MySimpleExecutor executor = new MySimpleExecutor(myConfiguration);
return new MyDefaultSqlSession(myConfiguration, executor);
}
}
SqlSessionFactory的构建
- MySqlSession
/**
* SqlSession对象,用于获取Dao对象
* @author shaofan
*
*/
public interface MySqlSession {
<T> T getMapper(Class<T> c) throws InstantiationException, IllegalAccessException;
}
- MyDefaultSqlSession,MySqlSession的默认实现
public class MyDefaultSqlSession implements MySqlSession{
private final MyConfiguration configuration;
private final MyExecutor executor;
public MyDefaultSqlSession(MyConfiguration configuration,MyExecutor executor) {
this.configuration = configuration;
this.executor = executor;
}
@Override
public <T> T getMapper(Class<T> c) throws InstantiationException, IllegalAccessException {
return MyAspect.getProxy(c,executor,configuration);
}
}
- MyAspect,代理切面,这里使用cglib的动态代理,由于Dao接口没有实现类,无法使用jdk动态代理
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 MyAspect{
public static <T>T getProxy(Class<T> c,MyExecutor executor,MyConfiguration configuration) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(c);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String key = method.getDeclaringClass().getName() + "." + method.getName();
return executor.execute(configuration.getMappers().get(key), args);
}
});
return (T)enhancer.create();
}
}
Executor
- MyExecutor
public interface MyExecutor {
Object execute(MyMappedStatement ms,Object parameter);
}
- MySimpleExecutor,MyExecutor的简单实现,解析Sql比较复杂,这里不做实现
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class MySimpleExecutor implements MyExecutor{
private Connection conn;
public MySimpleExecutor(MyConfiguration configuration) {
try {
Class.forName(configuration.getDriver());
conn = DriverManager.getConnection(configuration.getUrl(),configuration.getUsername(),configuration.getPassword());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
System.out.println("连接数据库成功");
}
@Override
public Object execute(MyMappedStatement ms, Object parameter) {
System.out.println("开始执行sql");
String type = ms.getSql().substring(0,ms.getSql().indexOf(' '));
if(type.contains("select")) {
doQuery(ms,parameter);
}else {
doUpdate(ms,parameter);
}
return null;
}
private <T> void doUpdate(MyMappedStatement ms, T parameter) {
String sql = ms.getSql();
System.out.println(sql);
}
private void doQuery(MyMappedStatement ms, Object parameter) {
String sql = ms.getSql();
System.out.println(sql);
}
}
MyBatis插件原理
MyBatis 的插件可以在不修改原来的代码的情况下,通过拦截的方式,改变四大核心对象的行为,比如处理参数,处理SQL,处理结果
Executor增强流程
MyBatis经典面试题
- MyBatis有什么优点?
免除了jdbc代码以及设置参数和结果集操作,简化了数据库访问操作
将对象与关系进行映射,省去了手动封装结果集的操作
MyBatis 把 sql 语句从 Java 源程序中独立出来,放在单独的 XML 文件中编写,给程序的维护带来了很大便利
相对于Mybatis-plus和hibernate这样的全自动orm映射,mybatis能够结合数据库特点灵活控制sql,在一些场景下效率更高,能够完成复杂场景的查询
- 写的数据访问对象(Dao)是一个接口,MyBatis是如何给我们返回一个对象的?
MyBatis的SqlSession通过动态代理的方式,给我们返回一个增强对象,我们在调用方法的时候,被代理的拦截器所拦截,然后通过方法名找到MappedStatement中对应的Sql交由Executor来执行
- #{}和${}的区别
#{}会进行预处理操作,${}不会进行
- 一级缓存和二级缓存的区别
一级缓存存在于SqlSession,是默认开启的,同一个SqlSession操作相同sql会从缓存中读取
二级缓存存在于SqlSessionFactory,默认不开启,不同SqlSession操作相同sql也会从缓存中读取(二级缓存的开启使用第三方如EHCache资源来提供缓存)
- MyBatis常用的注解有哪些?
@Select、@Update等映射增删改查的sql语句
@SelectProvider、@UpdateProvider等映射删改查的动态Sql
@Results映射结果集的列表(对应xml文件中的resultMapper标签)
@One、@Many用于多对一、一对多查询,对应xml文件中的association、collection标签
@Param用于传入多个参数时,为参数取名
@Options用于指定附加属性配置,比如useGeneratedKeys、fetchType
- MyBatis分页插件的原理
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
- 如何理解mybatis-plus
概念:MP是Mybatis的一个增强工具,它简化了MyBatis大量的编写sql的操作
优点:Mybatis-Plus是在Mybatis的基础上做了增强,却不做改变;使用Mybatis-Plus除了能简化开发,在复杂场景下又保留了mybatis原生功能
缺点(注意事项):Mybatis-plus虽然简化了大量开发,但容易造成代码层次混乱,我们可能把大量数据逻辑写道service甚至是controller中,所以使用的时候一定要注意分析,不要把所有数据操作都交给MP去实现
如何配置:Springboot项目中,在配置文件中将PO和数据表的映射关系配置好后,使Dao继承自BaseMapper即可
Thymeleaf
- 使用Thymeleaf有什么优点?(相对于JSP)
Thymeleaf能够于HTML语言兼容
Thymeleaf适合做静态化
- 如何理解静态化
概念:静态化是指通过程序将模板或数据生成为静态页面
优点:访问静态页面不用访问数据库,提高性能
缺点:数据的不具有时效性,用户看到的消息可能已经过时
如何实现静态化:使用nginx进行动静分离,将静态资源放在nginx服务器下
- 常见的Thymeleaf指令
th:text替换文本,th:each循环集合数据,th:if执行判断
Maven
- Maven的生命周期
Maven有三个独立的生命周期,分别为clean:清理项目、default:构建项目、site:建立和发布项目站点;每个生命周期方法执行的时候,会把当前生命周期的前面所有方法一次执行
- 如何管理多模块的项目依赖版本?
通过继承的方式,在父模块中声明
<dependencyManagement />
和<pluginManagement />
,然后让子模块通过元素指定父模块,这样子模块中定义以来就可以只定义groupId和artifactId,自动使用父模块的version
通过聚合的方式,使用<dependency />
时声明scope为import,从而引入一个pom的<dependencyManagement ./>
- Maven聚合与继承的区别
聚合是在父模块中配置module标签来指定子模块,接下来所有操作在父模块中进行可以对子模块做一并的操作
继承是在子模块使用parent标签来指定父模块,父模块的依赖子模块也会有,避免重复的依赖导入代码