首先了解传统JDBC实现逻辑:
public static void main(String[] args) { //Connection对象 Connection conn = null; //Statement对象 Statement stmt = null; //ResultSet对象 ResultSet rs = null; try { // 1.加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 2.获取数据库连接对象(这里用的是oracle数据库) conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/sales","root","root"); // 3.通过获得数据库连接对象获取一个statement对象 stmt = conn.prepareStatement(); // 4.通过statement对象执行sql语句,查询操作用executeQuery(),增删改用executeUpdate() rs = stmt.executeQuery("select * from user"); while(rs.next()){ //将读取的数据获取 int id = rs.getInt("id"); System.out.println(id); } } catch (Exception e) { e.printStackTrace(); }finally{ //最后关闭数据库连接,先打开的连接后关闭,后打开的先关闭 try { if(rs!=null){ rs.close(); } if(stmt!=null){ stmt.close(); } if(conn!=null){ conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }
1加载驱动
2获取connection 链接。
3.获取预编译 perpestatement
4.编写sql
5.去回传封装对象
传统JDBC缺陷:十比较严重的硬编码需要反复手动开关链接需要手动的封装数据。
对以上几个缺陷进行优化和设计:。
使用配置文件进行对相关的数据源配置好驱动加载,这样更换数据源也只需要修改相关配置文件。。
DB的链接资源的重复开销,可以使用数据库链接是池。 ↓
SQL的编码问题也同样可以使用配置文件进行编写,不会影响代码。。
使用动态代理的思想,反射实现对返回结果集的封装,避免手动封装 重复和多余的过程。。
实现思想:
配置文件部分:对数据源和SQL进行xml的格式进行配置。
读取配置:建立resource方法去读取配置文件部分。使用IO流把读取的配置信息放入流
public class Resources { // 根据配置文件路径,把配置文件加载为字节输入流 public static InputStream getResourceAsSteam(String path){ InputStream inputStream = Resources.class.getClassLoader().getResourceAsStream(path); return inputStream; } }
定义两个封装容器: MappedStatement用于存放SQL部分解析出来的信息(mapperxm).Configuration用于核心配置,封装好数据源相关配置信息(datasouce), 同时也可以把封装好的MappedStatement 也放入,这样以后向下传递的时候只需要一个对象即可。
MappedStatement :这个容器主要存储SQL的相关信息涉及查询类型(resutTpye) 识别Statementid识别Statementid由namespace (一般可以定义大类比如user. product等)加上当id (mapper内定义的id)作为唯一标识、以及mapper内的sgl语句。
public class XMLConfigBuilder { private Configuration configuration; public XMLConfigBuilder() { this.configuration = new Configuration(); } /** * 解析配置,创建封装Configuration */ public Configuration parseConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException { Document document = new SAXReader().read(inputStream); //<eConfiguration> Element rootElement = document.getRootElement(); List<Element> list = rootElement.selectNodes("//property"); Properties properties = new Properties(); for (Element element : list) { String name = element.attributeValue("name"); String value = element.attributeValue("value"); //封装 properties properties.setProperty(name,value); } ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); comboPooledDataSource.setDriverClass(properties.getProperty("driverClass")); comboPooledDataSource.setDriverClass(properties.getProperty("jdbcUrl")); comboPooledDataSource.setDriverClass(properties.getProperty("userName")); comboPooledDataSource.setDriverClass(properties.getProperty("passWord")); configuration.setDataSource(comboPooledDataSource); //mapper.xml解析 路径 List<Element> listMapper = rootElement.selectNodes("//mapper"); for (Element element : listMapper) { String resource = element.attributeValue("resource"); InputStream resourceAsSteam = Resources.getResourceAsSteam(resource); XMLMapprtBuilder xmlConfigBuilder = new XMLMapprtBuilder(configuration); xmlConfigBuilder.pase(resourceAsSteam); } return configuration; } }
public class XMLMapprtBuilder { private Configuration Configuration; public XMLMapprtBuilder(Configuration Configuration) { this.Configuration = Configuration; } public void pase(InputStream inputStream) throws DocumentException { Document document = new SAXReader().read(inputStream); Element rootElement = document.getRootElement(); //获取select标签里的内容 List<Element> elements = rootElement.selectNodes("//select"); String namespace = rootElement.attributeValue("namespace"); for (Element element : elements) { //解析 String id = element.attributeValue("id"); String resultType = element.attributeValue("resultType"); String paramterType = element.attributeValue("paramterType"); String sqlText = element.getTextTrim(); MappedStatement mappedStatement = new MappedStatement(); mappedStatement.setId(id); mappedStatement.setParamterType(paramterType); mappedStatement.setResultType(resultType); mappedStatement.setSql(sqlText); String key = namespace + "." + id; Configuration.getMappedStatementMap().put(key,mappedStatement); } } }
配置数据解析:使用Dom4j解析把解析后的数据通过反射给相关实体类(Configuration.MappedStatement)。
配置信息封装:使用两个类作为容器封装读取的配置信息向下传参。,
进入seesionFactory的建立:通过工程模式创建seesion。
public class SqlSessionFactoryBuilder { public SqlSessionFactory bulid(InputStream inputStream) throws DocumentException, PropertyVetoException { //1.使用log4j解析配置文件,封装好configuration //创建解析类 XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(); Configuration configuration = xmlConfigBuilder.parseConfiguration(inputStream); //2.创建传递SqlSessionFactory对象 /** * 创建SqlSessionFactory 产生session会话 */ DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration); return defaultSqlSessionFactory; } }
seesion内定义实现crud操作:对于返回结果集由于返回结果不样, 不用定义准确乏型。定义查询传参建议也留下可变参数。但是传入信息定会包括我们的configuration信息
package com.lagou.sqlSession; import com.lagou.config.BoundSql; import com.lagou.pojo.Configuration; import com.lagou.pojo.MappedStatement; import com.lagou.utils.GenericTokenParser; import com.lagou.utils.ParameterMapping; import com.lagou.utils.ParameterMappingTokenHandler; import sun.net.www.http.ChunkedOutputStream; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.sql.*; import java.util.ArrayList; import java.util.List; public class SimpleExecutor implements Executor { @Override public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception { //注册驱动,获取链接 Connection connection = configuration.getDataSource().getConnection(); //转换sql 把相关#{user_id} 变成占位? String sql = mappedStatement.getSql(); //解析sql BoundSql boundSql = getBoundSQL(sql); //获取预处理 PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText()); //设置参数 params //获取类全路径获取class String paramterType = mappedStatement.getParamterType(); Class<?> paramterTypeClass = getClassType(paramterType); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); String content = parameterMapping.getContent(); //反射 Field declaredField = paramterTypeClass.getDeclaredField(content); //暴力访问 declaredField.setAccessible(true); Object o = declaredField.get(params[0]); preparedStatement.setObject(i+1,o); } //执行sql ResultSet resultSet = preparedStatement.executeQuery(); //获取class String resultType = mappedStatement.getResultType(); Class<?> classTypeClass = getClassType(resultType); //返回结果集 List<Object> objects = new ArrayList<>(); while (resultSet.next()){ Object o = classTypeClass.newInstance(); //获取元数据 ResultSetMetaData metaData = resultSet.getMetaData(); for (int i = 0; i < metaData.getColumnCount(); i++) { //字段名 String columnName = metaData.getColumnName(i); Object value = resultSet.getObject(columnName); //使用反射,根据数据库字段和实体类对应,完成封装 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, classTypeClass); Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(o,value); } objects.add(o); } return (List<E>) objects; } private Class<?> getClassType(String paramterType) throws ClassNotFoundException { if(paramterType != null){ Class<?> aClass = Class.forName(paramterType); return aClass; } return null; } /** *把相关#{user_id} 变成占位?把user_id的值进行存储 * @param sql * @return */ private BoundSql getBoundSQL(String sql) { //标记处理: 解析相关占位 ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler(); GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler); //解析后的sql String paseSql = genericTokenParser.parse(sql); //#{}内的参数 List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings(); BoundSql boundSql = new BoundSql(paseSql, parameterMappings); return boundSql; } }
创建Excuter类进行相关的JDBC数据库链接操作:传入我们需要的MappedStatement相关sql信息,Configuration 的相关信息。创建相关的crud方法。。
例如创建query方法,使用Configuration内的datasouce数据库链接,。
从MappedStatement获取sg!.对编写的sql进行解析转换(自己mapper里sql的规进行解析使用占位符代替,再把里的值做出替换。)售
使用标记解析器.解析我们占位编译成可以执行的sgl. (使用 mybatis里的工具类包,。这个包负责把相关的占位符进行替换,以及获取我们#内的值,)中
获取实体类对象反射,暴力获取传入的实体类的相关属性值.
获取预处理对象prepareStatement把获取的值塞入执行sql.。
执行在返回结果集,对查询的结果数据set进入实体(实体类型再mapperxm指定了相关的实体类型),利用反射根据数据库(字段名cloumnName)对应数据(要注意实体类和数据库对应的表要对应)封装成实体类。
附带我项目的git:https://github.com/yhybadfisher/badfisherOwer/blob/master/MyBatis-easy.rar
自定优化
Dao层会出现大量的代码重复,解决:可以提取一个上层类进行继承减少重复代码。。部分硬编码(statmentid),.
不要Dao的实现类,使用代理模式为dao接口实现代理实现类。中
把方法名和id (mapper里的sql用的id)有相同的值,通过获取方法名加上权限类名(namespace)这样就可以解决相关获取sql和实体类。
原文:https://www.cnblogs.com/badfisher/p/12985064.html