使用jdbc连接数据库
在学习Mybatis之前我们需要先回顾以下如何使用jdbc连接数据库,下面是一个简单的例子。 例如我们这里有一张user表,表里面有两个字段。
字段名 | 类型 | 主键 |
---|---|---|
id | int | true |
name | varchar | false |
phone | varchar | false |
那么要当我们在数据库中查询出一条User记录时,我们要把它封装到一个User对象中,方便使用。
//省略get set方法public class User {private int id;private String name;private String phone; }复制代码
现在我们使用jdbc来对User表进行查询。
public class TestJdbc {public static void main(String[] args) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection(""); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("select * from user"); List<User> list = new LinkedList<>();while (resultSet.next()){int id = resultSet.getInt("id"); String name = resultSet.getString("name"); String phone = resultSet.getString("phone"); User user = new User(id,name,phone); list.add(user); } resultSet.close(); statement.close(); connection.close(); } }复制代码
这里大概有以下几个步骤。
- DriverManager注册驱动。
- 创建Connection 连接。
- 创建Statement(该对象用来执行sql语句并返回结果)。
- 执行sql并发到返回值。
- 对结果进行封装
- 关闭连接(按顺序)
可以看到,当我们使用jdbc来操作数据库,过程相当繁琐。当我们使用mybatis进行数据库操作
String resource = "mybatis.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> = mapper.findList(); sqlSession.close();复制代码
这里也有几个步骤
- 配置Mybatis
- 启动SqlSessionFactory sqlSessionFactory。
- 开启连接,拿到sqlSession
- 通过动态代理拿到mapper
- 关闭连接
可以看出Mybatis在使用起来更方便,对于语句分离和sql的执行,返回值的封装,都很方便。
这里面很重要的一步就是动态代理,通过mapper接口来执行具体的语句。我们可以自己写一个动态代理来模拟这个过程。
动态代理Demo
首先我们创建一个注解创建@Select,当然你也可以选择使用读取xml的方法,这里使用注解比较容易说明。
这个select用来传入一句sql
@Retention(value = RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Select {String value(); }复制代码
接着我们创建一个Mapper
public interface UserMapper {@Select("select * from user")List<User> findList(); }复制代码
那么我们现在就需要使用动态代理让UserMapper来执行具体的查询语句了。
public class TestJdbc {public static void main(String[] args) throws ClassNotFoundException, SQLException { UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(TestJdbc.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return null; } }); } }复制代码
这里是jdk自带的一个使用动态代理的方法, Proxy.newProxyInstance,第一个参数传入类加载器,第二个是个class数组,第三个是个 InvocationHandler接口。这个接口里面有个invode方法,也就是说当我执行findList的时候,实际上就会调用invoke的方法。
那么接下来的事情就很清楚了,我们只需要补充invoke的逻辑,就能执行执行数据库操作了。可以分为以下几步。
- 通过注解(或者XML)拿到sql语句
//获取注解上的sql Select select = method.getAnnotation(Select.class); String sql = select.value();复制代码
- 执行sql语句,并拿到返回值。
- 将返回值封装程我们需要的List<User>然后返回。
当然实际的场景不可能这么简单,mybatis需要解析更加复杂的sql,并对#{},和${}分别进行解析。并对sql进行分类,例如是select的还是update的,如果是select类型还要从缓存中查询。等等
拦截器Demo
我们首先需要一个拦截器接口,这个拦截器传入一个sql语句
interface Intercepter {String plugin(String sql); }复制代码
这个实现类将sql语句转成大写
private static class ToUpperCaseIntercepter implements Intercepter{ @Override public String plugin(String sql) { return sql.toUpperCase(); } }复制代码
这个实现类会将#{id},转化为具体的值1.
private static class ChangeValueIntercepter implements Intercepter{ @Override public String plugin(String target) { return target.replace("#{id}","1"); } }复制代码
之后就是我们的拦截器链了
private static class IntercepterChain { //拦截器的listList<Intercepter> list = new LinkedList<>();//添加方法public void addIntercepter(Intercepter intercepter){ list.add(intercepter); }//执行方法public String pluginAll(String sql){for (Intercepter intercepter : list) { sql = intercepter.plugin(sql); }return sql; } }复制代码
首先我们声明两个拦截器并添加到拦截器链中,然后执行pluginAll方法,这样我们的sql语句就会根据拦截器中的规则改变了。这两个拦截器的作用事将语句中#{id}替换成1,然后将语句转成大写。
在实际场景中可以对语句添加limit offset ,这样就能自动进行分页。
public class IntercepterDemo{public static void main(String[] args) { IntercepterChain intercepterChain = new IntercepterChain(); intercepterChain.addIntercepter( new ChangeValueIntercepter()); intercepterChain.addIntercepter( new ToUpperCaseIntercepter()); String sql = intercepterChain.pluginAll("select * from user where id = #{id}"); System.out.println(sql); } }复制代码
Mybatis也是类似,只不过添加拦截器是根据xml中的配置文件进行注入的。且pluginAll的参数有所变化。在后文中回详细说明。
Mybatis的架构分析
我们根据上文sql的执行流程来逐步分析.
在启动mybatis之前,我们首先需要解析和保存它的xml配置文件,所以我们需要一个Configuration对象。Mybatis大部分的类中,都持有这个Configuration对象来方便获取配置文件的信息。
当解析完配置文件后,就能执行sql语句了,这时候就需要一个Executor执行器,用来调度增删改查这些操作,做一些前置操作如获取mapper的sql语句,查询缓存等等。当然Executor并不需要手动进行创建,在我们openSession的时候就会自动进行创建。
执行完前置操作之后,就需要到数据库中进行具体的查询了。与jdbc类似,这里也有一个名为StatementHandler的对象来具体执行sql。 当然在这之前我们我要对传入的参数进行处理,时#{}类型的使用占位符,${}类型的直接进行替换。所以我们要用到处理参数的ParameterHandler。 最后我们要将返回的结果封装成具体的对象,那就需要ResultSetHandler来生成并封装对象。
ParameterHandler和ResultSetHandler是StatementHandler中的成员,会在创建StatementHandler的时候同步进行创建。
在后文中,会根据这个流程来具体分析sql执行的过程。对象和字段同样也是使用User.