1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Java软件开发:自定义MyBatis持久层框架

Java软件开发:自定义MyBatis持久层框架

时间:2021-01-10 13:55:45

相关推荐

Java软件开发:自定义MyBatis持久层框架

自定义MyBatis持久层框架

1 框架概述1.1 什么是框架1.2 框架要解决的问题1.3 软件开发的分层的重要性2 MyBatis框架3 JDBC编程3.1 JDBC程序的回顾3.2 JDBC存在的问题4 搭建MyBatis开发环境4.1 创建Maven工程4.2 编写实体类4.3 编写持久层接口4.4 编写持久层接口的映射文件4.5 编写 SqlMapConfig 配置文件4.6 编写测试类5 三种常用设计模式5.1 工厂模式5.2 代理模式5.3 建造者模式6 自定义MyBatis框架6.1 创建Maven工程6.2 引入工具类6.3 编写读取配置文件类6.4 编写Mapper类6.5 编写 Configuration 配置类6.6 编写构建者类6.7 编写 SqlSessionFactory 接口和实现类6.8 编写 SqlSession 接口和实现类6.9 编写用于创建 Dao 接口代理对象的类

1 框架概述

1.1 什么是框架

框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面,而后者是从目的方面给出的定义。

简而言之,框架其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别人搭好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。

1.2 框架要解决的问题

框架要解决的最重要的一个问题是技术整合的问题,在 J2EE 的框架中,有着各种各样的技术,不同的软件企业需要从 J2EE 中选择不同的技术,这就使得软件企业最终的应用依赖于这些技术,技术自身的复杂性和技术的风险性将会直接对应用造成冲击。

而应用是软件企业的核心,是竞争力的关键所在,因此应该将应用自身的设计和具体的实现技术解耦。这样,软件企业的研发将集中在应用的设计上,而不是具体的技术实现,技术实现是应用的底层支撑,它不应该直接对应用产生影响。

框架一般处在低层应用平台(如 J2EE)和高层业务逻辑之间的中间层。

1.3 软件开发的分层的重要性

框架的重要性在于它实现了部分功能,并且能够很好的将低层应用平台和高层业务逻辑进行了缓和。为了实现软件工程中的“高内聚、低耦合”。把问题划分开来各个解决,易于控制,易于延展,易于分配资源。我们常见的MVC 软件设计思想就是很好的分层思想。

通过分层更好的实现了各个部分的职责,在每一层将再细化出不同的框架,分别解决各层关注的问题。

2 MyBatis框架

mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。

mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。

采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。

3 JDBC编程

3.1 JDBC程序的回顾

import java.sql.*;public class Main {public static void main(String[] args) {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;String url = "jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf-8";String driver = "com.mysql.jdbc.Driver";String username = "root";String password = "Root@";try {//加载数据库驱动Class.forName(driver);//通过驱动管理类获取数据库连接connection = DriverManager.getConnection(url, username, password);//定义Sql语句,?标识占位符String sql = "select * from user where username = ?";//获取预处理statementpreparedStatement = connection.prepareStatement(sql);//设置参数,第一个参数是sql语句中参数的序号(从1开始),第二个参数设置的是参数值preparedStatement.setString(1, "老王");//数据库执行查询,返回查询结果集resultSet = preparedStatement.executeQuery();//遍历查询结果集while (resultSet.next()) {System.out.println(resultSet.getString("id") + "," + resultSet.getString("username"));}} catch (Exception e) {e.printStackTrace();} finally {if (resultSet != null) {try {resultSet.close();} catch (SQLException throwables) {throwables.printStackTrace();}}if (preparedStatement != null) {try {preparedStatement.close();} catch (SQLException throwables) {throwables.printStackTrace();}}if (connection != null) {try {connection.close();} catch (SQLException throwables) {throwables.printStackTrace();}}}}}

3.2 JDBC存在的问题

1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。

2、Sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java代码。

3、使用 preparedStatement 向占有位符号传参数存在硬编码,因为 sql 语句的 where 条件不一定,可能多也可能少,修改 sql 还要修改代码,系统不易维护。

4、对结果集解析存在硬编码(查询列名),sql 变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成 pojo 对象解析比较方便。

4 搭建MyBatis开发环境

4.1 创建Maven工程

<dependencies><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.5</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.6</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.10</version><scope>test</scope></dependency></dependencies>

4.2 编写实体类

public class User implements Serializable {private Integer id;private String username;private Date birthday;private String sex;private String address;@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", birthday=" + birthday +", sex='" + sex + '\'' +", address='" + address + '\'' +'}';}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}}

4.3 编写持久层接口

public interface IUserDao {/*** 查询所有* @return*/List<User> findAll();}

4.4 编写持久层接口的映射文件

创建位置:必须和持久层接口在相同的包中。

名称:必须以持久层接口名称命名文件名,扩展名是.xml

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapperPUBLIC "-////DTD Mapper 3.0//EN""/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.baidu.dao.IUserDao"><!-- 配置查询所有--><select id="findAll" resultType="pany.domain.User">select * from user</select></mapper>

4.5 编写 SqlMapConfig 配置文件

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configurationPUBLIC "-////DTD Config 3.0//EN""/dtd/mybatis-3-config.dtd"><configuration><!--配置环境--><environments default="mysql"><!--配置mysql环境--><environment id="mysql"><!--配置事务类型--><transactionManager type="JDBC"></transactionManager><!--配置数据源(连接池)--><dataSource type="POOLED"><!--配置连接数据库的四个基本信息--><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatisdb"/><property name="username" value="root"/><property name="password" value="Root@"/></dataSource></environment></environments><!--指定映射配置文件的位置--><mappers><mapper resource="com/company/dao/IUserDao.xml"/></mappers></configuration>

4.6 编写测试类

public class MybatisTest {public static void main(String[] args) throws Exception {//1.读取配置文件InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");//2.创建SqlSessionFactory工厂SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();SqlSessionFactory factory = builder.build(in);//3.使用工厂生产SqlSession对象SqlSession session = factory.openSession();//4.使用SqlSession创建DAO的代理对象IUserDao userDao = session.getMapper(IUserDao.class);//5.使用代理对象执行方法List<User> users = userDao.findAll();for (User user : users) {System.out.println(user);}//6.释放资源session.close();in.close();}}

5 三种常用设计模式

5.1 工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

主要解决:主要解决接口选择的问题。

何时使用:我们明确地计划不同条件下创建不同实例时。

如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。

关键代码:创建过程在其子类执行。

5.2 代理模式

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

意图:为其他对象提供一种代理以控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

何时使用:想在访问一个类时做一些控制。

如何解决:增加中间层。

关键代码:实现与被代理类组合。

5.3 建造者模式

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。

意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。

何时使用:一些基本部件不会变,而其组合经常变化的时候。

如何解决:将变与不变分离开。

关键代码:建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。

6 自定义MyBatis框架

6.1 创建Maven工程

<dependencies><!-- <dependency>--><!-- <groupId>org.mybatis</groupId>--><!-- <artifactId>mybatis</artifactId>--><!-- <version>3.4.5</version>--><!-- </dependency>--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.6</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.10</version><scope>test</scope></dependency><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.1.6</version></dependency></dependencies>

6.2 引入工具类

public class XMLConfigBuilder {/*** 解析主配置文件,把里面的内容填充到DefaultSqlSession所需要的地方* 使用的技术:* dom4j+xpath*/public static Configuration loadConfiguration(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"//把映射配置文件的内容获取出来,封装成一个mapMap<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);}}//返回Configurationreturn 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();//创建KeyString key = namespace + "." + id;//创建ValueMapper 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;}}

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 userString resultType = mapper.getResultType();//com.itheima.domain.UserClass domainClass = Class.forName(resultType);//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();//取出结果集的元信息:ResultSetMetaDataResultSetMetaData 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();//把获取的列的值,给对象赋值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();}}}}

public class DataSourceUtil {public static Connection getConnection(Configuration cfg) {try {Class.forName(cfg.getDriver());return DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());} catch (Exception e) {throw new RuntimeException(e);}}}

6.3 编写读取配置文件类

/*** 使用类加载器读取配置文件*/public class Resources {/*** 根据传入的参数,获取一个字节输入流** @param filepath* @return*/public static InputStream getResourceAsStream(String filepath) {return Resources.class.getClassLoader().getResourceAsStream(filepath);}}

6.4 编写Mapper类

/*** 用于封装执行的SQL语句和结果类型的全限定类名*/public class Mapper {private String queryString;//SQLprivate 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;}}

6.5 编写 Configuration 配置类

public class Configuration {private String driver;//驱动private String url;//地址private String username;//用户名private String password;//密码//Map<唯一标识,Mapper>用于保存映射文件中的sql标识及sql语句private Map<String, Mapper> mappers;public Map<String, Mapper> getMappers() {return mappers;}public void setMappers(Map<String, Mapper> mappers) {//此处需要使用追加的方式this.mappers = mappers;}public String getDriver() {return driver;}public void setDriver(String driver) {this.driver = driver;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}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;}}

6.6 编写构建者类

/*** 用于创建一个SqlSessionFactory对象*/public class SqlSessionFactoryBuilder {public SqlSessionFactory build(InputStream in) {DefaultSqlSessionFactory factory = new DefaultSqlSessionFactory();factory.setConfig(in);return factory;}}

6.7 编写 SqlSessionFactory 接口和实现类

public interface SqlSessionFactory {SqlSession openSession();}

/*** SqlSessionFactory接口的实现类*/public class DefaultSqlSessionFactory implements SqlSessionFactory {private InputStream config = null;public void setConfig(InputStream config) {this.config = config;}/*** 用于创建一个新的操作数据库对象** @return*/@Overridepublic SqlSession openSession() {Configuration cfg = XMLConfigBuilder.loadConfiguration(config);return new DefaultSqlSession(cfg);}}

6.8 编写 SqlSession 接口和实现类

/*** 自定义Mybatis中和数据库交互的核心类* 里面可以创建dao接口的代理对象*/public interface SqlSession {/*** 根据参数创建一个代理对象* @param daoInterfaceClass dao的接口字节码* @param <T>* @return*/<T> T getMapper(Class<T> daoInterfaceClass);/*** 释放资源*/void close();}

/*** SqlSession实现类*/public class DefaultSqlSession implements SqlSession {private Configuration cfg;private Connection conn;public DefaultSqlSession(Configuration cfg) {this.cfg = cfg;this.conn = DataSourceUtil.getConnection(cfg);}/*** 用于创建代理对象** @param daoInterfaceClass dao的接口字节码* @param <T>* @return*/@Overridepublic <T> T getMapper(Class<T> daoInterfaceClass) {return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),new Class[]{daoInterfaceClass},new MapperProxy(cfg.getMappers(), conn));}/*** 用于释放资源*/@Overridepublic void close() {if (conn != null) {try {conn.close();} catch (SQLException throwables) {throwables.printStackTrace();}}}//查询所有的方法public <E> List<E> selectList(String statement) {Mapper mapper = cfg.getMappers().get(statement);return new Executor().selectList(mapper, conn);}}

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

public class MapperProxy implements InvocationHandler {//Map的key是全限定类名+方法名private Map<String, Mapper> mappers;private Connection conn;public MapperProxy(Map<String, Mapper> mappers, Connection conn) {this.mappers = mappers;this.conn = conn;}/*** 用于对方法进行增强,就是调用SelectList方法** @param proxy* @param method* @param args* @return* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//1.获取方法名String methodName = method.getName();//2.获取方法所在类的名称String className = method.getDeclaringClass().getName();//3.组合keyString key = className + "." + methodName;//4.获取Mappers中的mapper对象Mapper mapper = mappers.get(key);//5.判断是否有mapperif (mapper == null) {throw new IllegalArgumentException("传入的参数有误");}Executor executor = new Executor();//6.调用工具类查询所有return executor.selectList(mapper,conn);}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。