自定义MyBatis框架

1 创建 Maven 工程

2 引入相关坐标

<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>

3 引入工具类到项目中

/**
* @author 黑马程序员
* @Company http://www.ithiema.com
* 用于解析配置文件
*/
public class XMLConfigBuilder {
 /**
 * 解析主配置文件,把里面的内容填充到 DefaultSqlSession 所需要的地方
 * 使用的技术:
 * dom4j+xpath
 * @param session
 */
 public static void loadConfiguration(DefaultSqlSession session,InputStream 
config){
 try{
 //定义封装连接信息的配置对象(mybatis 的配置对象)
 Configuration cfg = new Configuration();
 //1.获取 SAXReader 对象
 SAXReader reader = new SAXReader();
 //2.根据字节输入流获取 Document 对象
 Document document = reader.read(config);
//3.获取根节点
 Element root = document.getRootElement();
 //4.使用 xpath 中选择指定节点的方式,获取所有 property 节点
 List<Element> propertyElements = root.selectNodes("//property");
 //5.遍历节点
 for(Element propertyElement : propertyElements){
 //判断节点是连接数据库的哪部分信息
 //取出 name 属性的值
 String name = propertyElement.attributeValue("name");
 if("driver".equals(name)){
 //表示驱动
 //获取 property 标签 value 属性的值
 String driver = propertyElement.attributeValue("value");
 cfg.setDriver(driver);
 }
 if("url".equals(name)){
 //表示连接字符串
 //获取 property 标签 value 属性的值
 String url = propertyElement.attributeValue("value");
 cfg.setUrl(url);
 }
 if("username".equals(name)){
 //表示用户名
 //获取 property 标签 value 属性的值
 String username = propertyElement.attributeValue("value");
 cfg.setUsername(username);
 }
 if("password".equals(name)){
 //表示密码
 //获取 property 标签 value 属性的值
 String password = propertyElement.attributeValue("value");
 cfg.setPassword(password);
 }
 }
 //取出 mappers 中的所有 mapper 标签,判断他们使用了 resource 还是 class 属性
 List<Element> mapperElements = root.selectNodes("//mappers/mapper");
 //遍历集合
 for(Element mapperElement : mapperElements){
 //判断 mapperElement 使用的是哪个属性
 Attribute attribute = mapperElement.attribute("resource");
 if(attribute != null){
 System.out.println("使用的是 XML");
 //表示有 resource 属性,用的是 XML
 //取出属性的值
String mapperPath = attribute.getValue();// 获 取 属 性 的 值
"com/itheima/dao/IUserDao.xml"
 //把映射配置文件的内容获取出来,封装成一个 map
 Map<String,Mapper> mappers = loadMapperConfiguration(mapperPath);
 //给 configuration 中的 mappers 赋值
 cfg.setMappers(mappers);
 }else{
 System.out.println("使用的是注解");
 //表示没有 resource 属性,用的是注解
 //获取 class 属性的值
 String daoClassPath = mapperElement.attributeValue("class");
 //根据 daoClassPath 获取封装的必要信息
 Map<String,Mapper> mappers = loadMapperAnnotation(daoClassPath);
 //给 configuration 中的 mappers 赋值
 cfg.setMappers(mappers);
 }
 }
 //把配置对象传递给 DefaultSqlSession
 session.setCfg(cfg);
 
 }catch(Exception e){
 throw new RuntimeException(e);
 }finally{
 try {
 config.close();
 }catch(Exception e){
 e.printStackTrace();
 }
 }
 }
 /**
 * 根据传入的参数,解析 XML,并且封装到 Map 中
 * @param mapperPath 映射配置文件的位置
 * @return map 中包含了获取的唯一标识(key 是由 dao 的全限定类名和方法名组成)
 * 以及执行所需的必要信息(value 是一个 Mapper 对象,里面存放的是执行的 SQL 语句和
要封装的实体类全限定类名)
 */
 private static Map<String,Mapper> loadMapperConfiguration(String 
mapperPath)throws IOException {
 InputStream in = null;
 try{
 //定义返回值对象
 Map<String,Mapper> mappers = new HashMap<String,Mapper>();
//1.根据路径获取字节输入流
 in = Resources.getResourceAsStream(mapperPath);
 //2.根据字节输入流获取 Document 对象
 SAXReader reader = new SAXReader();
 Document document = reader.read(in);
 //3.获取根节点
 Element root = document.getRootElement();
 //4.获取根节点的 namespace 属性取值
 String namespace = root.attributeValue("namespace");//是组成 map 中 key 的
部分
 //5.获取所有的 select 节点
 List<Element> selectElements = root.selectNodes("//select");
 //6.遍历 select 节点集合
 for(Element selectElement : selectElements){
 //取出 id 属性的值 组成 map 中 key 的部分
 String id = selectElement.attributeValue("id");
 //取出 resultType 属性的值 组成 map 中 value 的部分
 String resultType = selectElement.attributeValue("resultType");
 //取出文本内容 组成 map 中 value 的部分
 String queryString = selectElement.getText();
 //创建 Key
 String key = namespace+"."+id;
 //创建 Value
 Mapper mapper = new Mapper();
 mapper.setQueryString(queryString);
 mapper.setResultType(resultType);
 //把 key 和 value 存入 mappers 中
 mappers.put(key,mapper);
 }
 return mappers;
 }catch(Exception e){
 throw new RuntimeException(e);
 }finally{
 in.close();
 }
 }
 /**
 * 根据传入的参数,得到 dao 中所有被 select 注解标注的方法。
 * 根据方法名称和类名,以及方法上注解 value 属性的值,组成 Mapper 的必要信息
 * @param daoClassPath
 * @return
 */
 private static Map<String,Mapper> loadMapperAnnotation(String daoClassPath)throws Exception{
 //定义返回值对象
 Map<String,Mapper> mappers = new HashMap<String, Mapper>();
 //1.得到 dao 接口的字节码对象
 Class daoClass = Class.forName(daoClassPath);
 //2.得到 dao 接口中的方法数组
 Method[] methods = daoClass.getMethods();
 //3.遍历 Method 数组
 for(Method method : methods){
 //取出每一个方法,判断是否有 select 注解
 boolean isAnnotated = method.isAnnotationPresent(Select.class);
 if(isAnnotated){
 //创建 Mapper 对象
 Mapper mapper = new Mapper();
 //取出注解的 value 属性值
 Select selectAnno = method.getAnnotation(Select.class);
 String queryString = selectAnno.value();
 mapper.setQueryString(queryString);
 //获取当前方法的返回值,还要求必须带有泛型信息
 Type type = method.getGenericReturnType();//List<User>
 //判断 type 是不是参数化的类型
 if(type instanceof ParameterizedType){
 //强转
 ParameterizedType ptype = (ParameterizedType)type;
 //得到参数化类型中的实际类型参数
 Type[] types = ptype.getActualTypeArguments();
 //取出第一个
 Class domainClass = (Class)types[0];
 //获取 domainClass 的类名
 String resultType = domainClass.getName();
 //给 Mapper 赋值
 mapper.setResultType(resultType);
 }
 //组装 key 的信息
 //获取方法的名称
 String methodName = method.getName();
 String className = method.getDeclaringClass().getName();
 String key = className+"."+methodName;
 //给 map 赋值
 mappers.put(key,mapper);
 }
 }
 return mappers;
}
}
/**
* @author 黑马程序员
* @Company http://www.ithiema.com
* 负责执行 SQL 语句,并且封装结果集
*/
public class Executor {
 public <E> List<E> selectList(Mapper mapper, Connection conn) {
 PreparedStatement pstm = null;
 ResultSet rs = null;
 try {
 //1.取出 mapper 中的数据
 String queryString = mapper.getQueryString();//select * from user
 String resultType = mapper.getResultType();//com.itheima.domain.User
 Class domainClass = Class.forName(resultType);//User.class
 //2.获取 PreparedStatement 对象
 pstm = conn.prepareStatement(queryString);
 //3.执行 SQL 语句,获取结果集
 rs = pstm.executeQuery();
 //4.封装结果集
 List<E> list = new ArrayList<E>();//定义返回值
 while(rs.next()) {
 //实例化要封装的实体类对象
 E obj = (E)domainClass.newInstance();//User 对象
 //取出结果集的元信息:ResultSetMetaData
 ResultSetMetaData rsmd = rs.getMetaData();
 //取出总列数
 int columnCount = rsmd.getColumnCount();
 //遍历总列数
 for (int i = 1; i <= columnCount; i++) {
 //获取每列的名称,列名的序号是从 1 开始的
 String columnName = rsmd.getColumnName(i);
 //根据得到列名,获取每列的值
 Object columnValue = rs.getObject(columnName);
 //给 obj 赋值:使用 Java 内省机制(借助 PropertyDescriptor 实现属性的封装)
 PropertyDescriptor pd = new
PropertyDescriptor(columnName,domainClass);//要求:实体类的属性和数据库表的列名保持一种
 //获取它的写入方法
Method writeMethod = pd.getWriteMethod();//setUsername(String 
username);
 //把获取的列的值,给对象赋值
 writeMethod.invoke(obj,columnValue);
 }
 //把赋好值的对象加入到集合中
 list.add(obj);
 }
 return list;
 } catch (Exception e) {
 throw new RuntimeException(e);
 } finally {
 release(pstm,rs);
 }
 }
 private void release(PreparedStatement pstm,ResultSet rs){
 if(rs != null){
 try {
 rs.close();
 }catch(Exception e){
 e.printStackTrace();
 }
 }
 if(pstm != null){
 try {
 pstm.close();
 }catch(Exception e){
 e.printStackTrace();
 }
 }
 }
}
/**
* 
* <p>Title: DataSourceUtil</p>
* <p>Description: 数据源的工具类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class DataSourceUtil {
/**
* 获取连接
* @param cfg
* @return
*/
public static Connection getConnection(Configuration cfg) {
try {
Class.forName(cfg.getDriver());
Connection conn = 
DriverManager.getConnection(cfg.getUrl(),cfg.getUsername() , cfg.getPassword());
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

4 编写 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:///eesy" ></property> 
 <property name="username" value="root"></property>
 <property name="password" value="1234"></property>
 </dataSource> 
 </environment> 
 </environments>
</configuration> 

注意:
此处我们直接使用的是 mybatis 的配置文件,但是由于我们没有使用 mybatis 的 jar 包,所以要把配
置文件的约束删掉否则会报错(如果电脑能接入互联网,不删也行)

5 编写读取配置文件类

/**
* 
* <p>Title: Resources</p>
* <p>Description: 用于读取配置文件的类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class Resources {
/**
* 用于加载 xml 文件,并且得到一个流对象
* @param xmlPath
* @return
* 在实际开发中读取配置文件:
* 第一:使用类加载器。但是有要求:a 文件不能过大。 b 文件必须在类路径下(classpath)
* 第二:使用 ServletContext 的 getRealPath()
*/
public static InputStream getResourceAsStream(String xmlPath) {
return Resources.class.getClassLoader().getResourceAsStream(xmlPath);
}
}

6 编写 Mapper 类

/**
* 
* <p>Title: Mapper</p>
* <p>Description: 用于封装查询时的必要信息:要执行的 SQL 语句和实体类的全限定类名</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class Mapper {
private String queryString;//sql
private String resultType;//结果类型的全限定类名
public String getQueryString() {
return queryString;
}
public void setQueryString(String queryString) {
this.queryString = queryString;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
}

7 编写 Configuration 配置类

/**
* 核心配置类
* 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;
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;
}
}

8 编写 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 + "]";
}
}

基于 XML 的自定义 mybatis 框架

1 编写持久层接口和 IUserDao.xml

/**
* 
* <p>Title: IUserDao</p>
* <p>Description: 用户的持久层操作</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
List<User> findAll();
}
<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
</mapper>

2 编写构建者类

/**
* 
* <p>Title: SqlSessionFactoryBuilder</p>
* <p>Description: 用于构建 SqlSessionFactory 的</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class SqlSessionFactoryBuilder {
/**
* 根据传入的流,实现对 SqlSessionFactory 的创建
* @param in 它就是 SqlMapConfig.xml 的配置以及里面包含的 IUserDao.xml 的配置
* @return
*/
public SqlSessionFactory build(InputStream in) {
DefaultSqlSessionFactory factory = new DefaultSqlSessionFactory();
//给 factory 中 config 赋值
factory.setConfig(in);
return factory;
}
}

3 编写 SqlSessionFactory 接口和实现类

/**
* 
* <p>Title: SqlSessionFactory</p>
* <p>Description: SqlSessionFactory 的接口</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public interface SqlSessionFactory {
/**
* 创建一个新的 SqlSession 对象
* @return
*/
SqlSession openSession();
}
/**
* 
* <p>Title: DefaultSqlSessionFactory</p>
* <p>Description:SqlSessionFactory 的默认实现 </p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private InputStream config = null;
public void setConfig(InputStream config) {
this.config = config;
}
@Override
public SqlSession openSession() {
DefaultSqlSession session = new DefaultSqlSession();
//调用工具类解析 xml 文件
XMLConfigBuilder.loadConfiguration(session, config);
return session;
}
}

4 编写 SqlSession 接口和实现类

/**
* 
* <p>Title: SqlSession</p>
* <p>Description: 操作数据库的核心对象</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public interface SqlSession {
/**
* 创建 Dao 接口的代理对象
* @param daoClass
* @return
*/
<T> T getMapper(Class<T> daoClass);
/**
* 释放资源
*/
void close();
}
/**
* 
* <p>Title: DefaultSqlSession</p>
* <p>Description: SqlSession 的具体实现</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class DefaultSqlSession implements SqlSession {
 //核心配置对象
private Configuration cfg;
public void setCfg(Configuration cfg) {
this.cfg = cfg;
}
//连接对象
private Connection conn;
//调用 DataSourceUtils 工具类获取连接
public Connection getConn() {
try {
conn = DataSourceUtil.getDataSource(cfg).getConnection();
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 动态代理:
* 涉及的类:Proxy
* 使用的方法:newProxyInstance
* 方法的参数:
* ClassLoader:和被代理对象使用相同的类加载器,通常都是固定的
* Class[]:代理对象和被代理对象要求有相同的行为。(具有相同的方法)
* InvocationHandler:如何代理。需要我们自己提供的增强部分的代码
*/
@Override
public <T> T getMapper(Class<T> daoClass) {
conn = getConn();
System.out.println(conn);
T daoProxy = (T) Proxy.newProxyInstance(daoClass.getClassLoader(),new
Class[] {daoClass}, new MapperProxyFactory(cfg.getMappers(),conn));
return daoProxy;
}
//释放资源
@Override
public void close() {
try {
System.out.println(conn);
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//查询所有方法
public <E> List<E> selectList(String statement){
Mapper mapper = cfg.getMappers().get(statement);
return new Executor().selectList(mapper,conn);
}
}

5 编写用于创建 Dao 接口代理对象的类

/**
* 
* <p>Title: MapperProxyFactory</p>
* <p>Description: 用于创建代理对象是增强方法</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class MapperProxyFactory implements InvocationHandler {
private Map<String,Mapper> mappers;
private Connection conn;
public MapperProxyFactory(Map<String, Mapper> mappers,Connection conn) {
this.mappers = mappers;
this.conn = conn;
}
/**
* 对当前正在执行的方法进行增强
* 取出当前执行的方法名称
* 取出当前执行的方法所在类
* 拼接成 key
* 去 Map 中获取 Value(Mapper)
* 使用工具类 Executor 的 selectList 方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
{
//1.取出方法名
String methodName = method.getName();
//2.取出方法所在类名
String className = method.getDeclaringClass().getName();
//3.拼接成 Key
String key = className+"."+methodName;
//4.使用 key 取出 mapper
Mapper mapper = mappers.get(key);
if(mapper == null) {
throw new IllegalArgumentException("传入的参数有误,无法获取执行的必要条件
");
}
//5.创建 Executor 对象
Executor executor = new Executor();
return executor.selectList(mapper, conn);
}
}

6 运行测试类

/**
* 
* <p>Title: MybatisTest</p>
* <p>Description: 测试 mybatis 的环境</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class MybatisTest {
public static void main(String[] args)throws Exception {
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建 SqlSessionFactory 的构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.使用构建者创建工厂对象 SqlSessionFactory
SqlSessionFactory factory = builder.build(in);
//4.使用 SqlSessionFactory 生产 SqlSession 对象
SqlSession session = factory.openSession();
//5.使用 SqlSession 创建 dao 接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//6.使用代理对象执行查询所有方法
List<User> users = userDao.findAll();
for(User user : users) {
System.out.println(user);
}
//7.释放资源
session.close();
in.close();
}
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值