自定义MyBatis框架

1 创建 Maven 工程

2 引入相关坐标




log4j
log4j
1.2.12



dom4j
dom4j
1.6.1



mysql
mysql-connector-java
5.1.6



jaxen
jaxen
1.1.6


junit
junit
4.10

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 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 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 mappers = loadMapperConfiguration(mapperPath);
 //给 configuration 中的 mappers 赋值
 cfg.setMappers(mappers);
 }else{
 System.out.println("使用的是注解");
 //表示没有 resource 属性,用的是注解
 //获取 class 属性的值
 String daoClassPath = mapperElement.attributeValue("class");
 //根据 daoClassPath 获取封装的必要信息
 Map 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 loadMapperConfiguration(String 
mapperPath)throws IOException {
 InputStream in = null;
 try{
 //定义返回值对象
 Map mappers = new HashMap();
//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 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 loadMapperAnnotation(String daoClassPath)throws Exception{
 //定义返回值对象
 Map mappers = new HashMap();
 //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
 //判断 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  List 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 list = new ArrayList();//定义返回值
 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();
 }
 }
 }
}
/**
* 
* 

Title: DataSourceUtil

*

Description: 数据源的工具类

*

Company: http://www.itheima.com/

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

 
 
  
  
  
  
  
  
 
 
  
  
 
 

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

5 编写读取配置文件类

/**
* 
* 

Title: Resources

*

Description: 用于读取配置文件的类

*

Company: http://www.itheima.com/

*/ 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 类

/**
* 
* 

Title: Mapper

*

Description: 用于封装查询时的必要信息:要执行的 SQL 语句和实体类的全限定类名

*

Company: http://www.itheima.com/

*/ 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 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 getMappers() {
return mappers;
}
public void setMappers(Map 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

/**
* 
* 

Title: IUserDao

*

Description: 用户的持久层操作

*

Company: http://www.itheima.com/

*/ public interface IUserDao { /** * 查询所有用户 * @return */ List findAll(); }

2 编写构建者类

/**
* 
* 

Title: SqlSessionFactoryBuilder

*

Description: 用于构建 SqlSessionFactory 的

*

Company: http://www.itheima.com/

*/ 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 接口和实现类

/**
* 
* 

Title: SqlSessionFactory

*

Description: SqlSessionFactory 的接口

*

Company: http://www.itheima.com/

*/ public interface SqlSessionFactory { /** * 创建一个新的 SqlSession 对象 * @return */ SqlSession openSession(); } /** * *

Title: DefaultSqlSessionFactory

*

Description:SqlSessionFactory 的默认实现

*

Company: http://www.itheima.com/

*/ 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 接口和实现类

/**
* 
* 

Title: SqlSession

*

Description: 操作数据库的核心对象

*

Company: http://www.itheima.com/

*/ public interface SqlSession { /** * 创建 Dao 接口的代理对象 * @param daoClass * @return */ T getMapper(Class daoClass); /** * 释放资源 */ void close(); } /** * *

Title: DefaultSqlSession

*

Description: SqlSession 的具体实现

*

Company: http://www.itheima.com/

*/ 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 getMapper(Class 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 List selectList(String statement){ Mapper mapper = cfg.getMappers().get(statement); return new Executor().selectList(mapper,conn); } }

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

/**
* 
* 

Title: MapperProxyFactory

*

Description: 用于创建代理对象是增强方法

*

Company: http://www.itheima.com/

*/ public class MapperProxyFactory implements InvocationHandler { private Map mappers; private Connection conn; public MapperProxyFactory(Map 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 运行测试类

/**
* 
* 

Title: MybatisTest

*

Description: 测试 mybatis 的环境

*

Company: http://www.itheima.com/

*/ 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 users = userDao.findAll(); for(User user : users) { System.out.println(user); } //7.释放资源 session.close(); in.close(); } }

 

你可能感兴趣的:(JAVA)