Mybatis笔记1 : 自定义Mybatis

  1.概述     

mybatis 通过 xml或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中 sql
的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。
采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我
们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。

1.1 jdbc 问题分析


1、 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解
决此问题。
2、 Sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大, sql 变动需要改变
java 代码。
3、 使用 preparedStatement 向占有位符号传参数存在硬编码,因为 sql 语句的 where 条件不一定,
可能多也可能少,修改 sql 还要修改代码,系统不易维护。
4、 对结果集解析存在硬编码(查询列名), sql 变化导致解析代码变化,系统不易维护,如果能将数
据库记录封装成 pojo 对象解析比较方便。

1.2  JDBC问题解决方法

 1.3 自定义Mybatis框架:

涉及到的一些知识点:
工厂模式(Factory 工厂模式)、构造者模式(Builder 模式)、代理模式,反射,自定义注解,注解的
反射, xml 解析,数据库元数据,元数据的反射等。
1.3.1  创建maven工程

引入相关的 maven 坐标
<dependencies>
<!-- 日志坐标 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<!-- 解析 xml 的 dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- dom4j 的依赖包 jaxen -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>

<version>4.10</version>
</dependency>
</dependencies>
1.3.2  引入工具类:

XMLConfigBuilder 类,用于实现 XML 文件解析的,解析工具目前采用的是 Dom4j 结合 xpath 实现。
Executor 类,用于实现 SQL 语句的执行,主要是调用 JDBC 来实现 SQL 语句的执行。
1.3.3 创建 SqlMapConfig.xml 配置文件

将数据库的连接信息抽取出来,放到一个 xml(SqlMapConfig.xml)文件中,后
面再去对此配置文件进行 xml 解析,这样就可以将配置文件中的信息读取出来,以便在 jdbc 代码中
直接使用这些数据库连接信息。
SqlMapConfig.xml 配置文件如下:

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<environments 
default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" >
</property>
<property name="url"value="jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf8" >
</property>
<property name="username" value="root">
</property>
<property name="password" value="root">
</property>
</dataSource>
</environment>
</environments>
</configuration>

1.3.4 加入用于保存 SQL 语句的类


将配置文件中的 SQL 语句还需要读取出来,定义一个 Mapper
类,用于将配置文件中的 SQL 语句保存起来,具体如下:

public class Mapper
 {
private String querySql;//sql 语句
private String resultType;//全限定类名
public String getQuerySql() 
{return querySql;
}
public void setQuerySql(String querySql) 
{
this.querySql = querySql;
}
public String getResultType() 
{return resultType;
}
public void setResultType(String resultType)
 {this.resultType = resultType;}
}


1.3.5  加入 Configuration 配置类
Configuration 配置类主要用于保存 SqlMapConfig.xml 文件中读取的 xml 结点的信息,以及映射的 SQL
语句的集合。 该类的代码如下:

/**
* 核心配置类
* 1.数据库信息
* 2.sql 的 map 集合
*/
public class Configuration {
private String username; //用户名
private String password;//密码
private String url;//地址
private String driver;//驱动
//map 集合 Map<唯一标识, Mapper> 用于保存映射文件中的 sql 标识及 sql 语句


private Map<String,Mapper> mappers;//SQL 语句
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 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 Map<String, Mapper> getMappers() {
return mappers;
}
public void setMappers(Map<String, Mapper> mappers) {
this.mappers = mappers;
}
}

1.3.6创建数据表及 User 实体类

User表:

 User类:

public class User implements Serializable {/序列化
private int id;
private String username;// 用户姓名
private String sex;// 性别
private Date birthday;// 生日
private String address;// 地址
//省略 getter 与 setter
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", sex=" + sex
+ ", birthday=" + birthday + ", address=" + address + "]";
}
}

1.3.7 创建自定义@Select 注解

@Retention(RetentionPolicy,RUNTIME)

public @interface  Select{

String  value();

}
1.3.8 使用@Select 注解开发 UserDao 接口
创建 UserDao 代理接口

使用ognl表达式
 

public interface UserDao {
@Select(value="select * from user")
public List<User> getAllUserByMybatis() throws Exception;
//ognl 表达式
/**
* sql
* --- #{username}
* username
* getUsername
* --- #{sex}
*
* insert into user (username,sex) values (user.get,#{sex})
* @param user
* @throws Exception
*/
// @Insert(value="insert into user (username,sex) values (#{username},#{sex})")
// public void insert(User user) throws Exception;
}

1.4 基于注解方式定义 Mybatis 框架

1.4.1按照工厂模式

自定义 SqlSession 接口
public interface SqlSession {
public <E> List<E> selectList(String mapperId) throws Exception;
/**
* 创建动态代理对象
* clazz : 接口的字节码
*/
public <E> E getMapper(Class clazz) throws Exception;
}
创建了一个用于生产产品的接口。其中 getMapper()方法可以用于模
拟按 mybatis 的代理方式来生成,而 selectList()方法可以模拟按 mybatis 的传统方式来生成。
1.4.2  定义Default SqlSession实现类

DefaultSqlSession 类主要用于生成 mapper 代理对象,以及执行 SQL 语句的方法。
编写 DefaultSqlSession.java 实现类:

public class DefaultSqlSession implements SqlSessi
private Configuration cfg;//封装 mybatis 配置文件
private Executor executor;//SQL 语句执行器
public DefaultSqlSession(Configuration cfg) {
this.cfg = cfg;
this.executor = new Executor(this.cfg);
}
/**
* 生成 mapper 的代理对象
*/
/**
* 生成 mapper 的代理对象
*/
public <E> E getMapper(Class clazz) throws Exception {
//创建 invocationHandler
MapperProxyFactory proxy = new MapperProxyFactory(this);
//创建动态代理对象
return (E) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]
{clazz}, proxy);
}

/**
* 查询列表
* 定位到 sql 语句
*/
public <E> List<E> selectList(String mapperId) throws Exception {
//通过 id 获取 Mapper 对象
Mapper mapper = cfg.getMappers().get(mapperId);
//通过 mapper 获取 sql 语句和返回值类型
String sql = mapper.getQuerySql();
String resultType = mapper.getResultType(); //返回值的全限定类名
//调用 SQL 语句的 Executor 执行器来执行 sql 语句
return executor.executeQuery(sql, resultType);
}
public Executor getExecutor() {
return executor;
}
}
1.4.3 代理模式MapperProxyFactory类

代理模式:

 

抽象角色:通过接口或抽象类声明真实角色实现的业务方法。

代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

模式结构:

一个是真正的你要访问的对象(目标类),一个是代理对象,真正对象与代理
对象实现同一个接口,先访问代理类再访问真正要访问的对象。

代理模式分为静态代理、动态代理。

静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

静态代理模式:

 

      要求:真实角色,代理角色;真实角色和代理角色要实现同一个接口,代理角色要持有真实角色的引用。

  在Java中线程的设计就使用了静态代理设计模式,其中自定义线程类实现Runable接口,Thread类也实现了Runalbe接口,在创建子线程的时候,传入了自定义线程类的引用,再通过调用start()方法,调用自定义线程对象的run()方法。实现了线程的并发执行。

 

复制代码

 1 public class Test2 {
 2 
 3     public static void main(String[] args) {
 4         //Runnable实现类对象,真实角色
 5         Thread1 role1 = new Thread1();
 6         //线程类代理角色,该类也实现了Runnable接口,代理角色
 7         Thread thread1 = new Thread(role1);//传入了真实角色的引用
 8         
 9         thread1.start();
10         
11     }
12 }
13 
14 class Thread1 implements Runnable {
15 
16     @Override
17     public void run() {
18         //TODO
19     }
20 
21 }

复制代码

 

  Thread对象调用线程的start()方法,在内部调用了真实角色的run()方法。

设计静态代理模式

原文地址:https://www.cnblogs.com/yxiaooutlook/p/7798563.html

 

 

  第一步,要有一个共同使用的接口

1 // 共同的接口
2 public interface Proxy {
3     public abstract void todo();
4 }

  代理角色和真实角色共同实现该接口,代理角色实现需要的功能。

 

 1 // 真实角色
 2 class RealityRole implements Proxy {
 3     
 4     @Override
 5     public void todo() {
 6         System.out.println("真实角色的功能");
 7     }
 8 }
 9 
10 // 代理角色
11 class ProxyRole implements Proxy {
12     // 持有代理角色的引用
13     private Proxy realityRole;
14     
15     public ProxyRole() {
16         
17     }
18     
19     //传入一个真实角色
20     public ProxyRole(Proxy role) {
21         realityRole = role;
22     }
23     @Override
24     public void todo() {
25         //在真实角色功能运行之前,代理角色做准备工作
26         doBefore();
27         //执行真实角色的功能
28         realityRole.todo();
29         //代理角色的收尾工作
30         doAfter();
31     }
32     private void doBefore() {
33         System.out.println("准备工作");
34     }  
35     private void doAfter() {
36         System.out.println("收尾工作");
37     }
38 }

 

  创建真实角色的对象和代理角色的对象,并将真实角色对象的引用传给代理角色,让代理角色去执行功能。

 

 

 1 public class Test {
 2     public static void main(String[] args) {
 3         //创建真实角色对象
 4         Proxy realityRole = new RealityRole();
 5         //创建代理角色对象,并制定真实对象
 6         ProxyRole proxyRole = new ProxyRole(realityRole);
 7         //代理角色工作,本质调用的还是真实角色的功能
 8         proxyRole.todo();
 9     }
10 }
运行结果:
  准备工作
  真实角色的功能
  收尾工作

菜鸟教程:https://www.runoob.com/design-pattern/proxy-pattern.html

注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。


1.4.2 MapperProxyFactory 类

因为 DefaultSqlSession 类(代理类)会自动去针对 mapper 接口生成它的代理实现类,而这个代理实现的过程就
会用到我们的 MapperProxyFactory 类。(真实类)
public class MapperProxyFactory implements InvocationHandler {
private DefaultSqlSession sqlSession;
public MapperProxyFactory(DefaultSqlSession sqlSession) {
this.sqlSession = sqlSession;
}
 

/**
* proxy:代理对象的引用
* 获取代理对象这个实现类的接口类型
* 获取当前接口的全限定类名
* method:当前执行的方法对象
* 当前执行的方法名称
*
* mapperId = 获取当前接口的全限定类名 + “.” + 当前执行的方法名称
* args:当前参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
//判断当前方法上是否存在 select 注解
boolean present = method.isAnnotationPresent(Select.class);
if(present) {//存在 select 注解
//1.获取 sql 语句
Select select = method.getAnnotation(Select.class);//获取当前方法上的 select 注解
String sql = select.value();
String returnType = method.getGenericReturnType().toString();
if(returnType.startsWith(List.class.getName())){
ParameterizedType pt =
(ParameterizedType)method.getGenericReturnType();
Class clz = (Class)pt.getActualTypeArguments()[0];
returnType = clz.getName();
}
//2.得到返回值类型
return sqlSession.getExecutor().executeQuery(sql, returnType);
}else {//没有注解
//invoke 方法:就是在查询数据库
//获取当前接口的全限定类名
//getGenericInterfaces:获取实体类的所有接口
String className =
proxy.getClass().getGenericInterfaces()[0].getTypeName();
//获取当前执行的方法名称
String methodName = method.getName();
String mapperId = className + "." + methodName;
return sqlSession.selectList(mapperId);
}
}

 

在上面的代码中 Select selectAnn = method.getAnnotation(Select.class);使用注解的反射来得到注解对
象,再通过 String returnType = method.getGenericReturnType().toString();来得到注解的元数据,从而
得到 返回值类 型。 为了 能够得到 List<T>泛型 集合 T 的类型 ,我们可 以使用反 射来完 成
ParameterizedType pt = (ParameterizedType)method.getGenericReturnType();
Class clz = (Class)pt.getActualTypeArguments()[0];//得到实际的 T 类型的 Class 实例,再通过 Class 的
getName()方法得到 T 的类全限定名。
 


1.4.2   使用构建者模式来创建 SqlSessionFactoryBuilder 类

构造者(建造者)模式

https://www.runoob.com/w3cnote/builder-pattern.html

 

/* 1.构建者模式* 
2.用来创建 SqlSessionFactory*
 3.隐藏工厂的构建细节*
 * 解析 xml 配置文件* *
 构建一个 Configuration 对象*/
public class SqlSessionFactoryBuilder 
{
/*** 解析 xml* dom4j* saxReader* reader 方法(字节流)
* @param is* 
@return*/
public SqlSessionFactory build(InputStream is) 
throws Exception
 {//1.构造 Configuration 调用 XMLConfigBuilder 类实现 xml 解析Configuration 
cfg = XMLConfigBuilder.buildConfiguration(is);
return new SqlSessionFactory(cfg);}}
}


上面的类中调用了 XMLConfigBuilder 类中读取 xml 文件并构建出 Configuration 对象的方法。最后通
过 Configuration 对象来构造 SqlSessionFactory 对象。 此时我们已经将构建者模式和工厂模式结合,
而工厂模式和动态代理模式结合.

 1.4.3   SqlSessionFactory 类

SqlSessionFactory 类如下:
/**
* SqlSession 工厂
* 1.工厂模式
* 2.获取 SqlSession
*/
public class SqlSessionFactory {
private Configuration cfg ;
public SqlSessionFactory(Configuration cfg) {
this.cfg = cfg;
}
//获取 SqlSession
public SqlSession openSession() {
return new DefaultSqlSession(cfg);
}
}

SqlSessionFactory 类 主要用来 创建  sqlsession 对象,进一步通过 openSession()方法,包装了 SqlSession 的实现创建过程。

  1.4.4   编写测试类

 

public class UserDaoTest {
/**
* 动态代理对象中查询:
* mapperid:接口全限定类名+"."+执行的方法名
* mapperId: cn.itcast.user.dao.UserDao.getAllUserByMybatis
*/
@Test
public void testGetAllUserByMybatis() throws Exception {
InputStream is =
this.getClass().getClassLoader().getResourceAsStream("SqlMapConfig.xml");
//c 创建 SqlSessionFactory
SqlSessionFactory sessionFactory
= new SqlSessionFactoryBuilder().build(is);
//创建 sqlSession
SqlSession sqlSession = sessionFactory.openSession();
//获取接口的动态代理对象
UserDao userDao = sqlSession.getMapper(UserDao.class);
//调用方法查询
List<User> list = userDao.getAllUserByMybatis();
for (User user : list) {
System.out.println(user);
}
}
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值