目录
一、新建项目
二、新建模块
三、回顾JDBC
四、准备环境
五、使用dom4j解析xml文件
六、开始,编写Mapper解析API
1、自定义Resources类
2、定义Configuration类
3、定义MappedStatement类
4、定义XmlMapperBuilder类
5、更新一下UserMapper.xml和UserMapper接口
6、测试解析结果
七、解析config.xml文件
1、导入连接池依赖
2、定义XmlConfigBuilder类
3、测试
八、获取SQLSession
1、编写SqlSession接口
2、编写SqlSessionFactory接口
3、编写DefaultSqlSession类实现接口
4、编写DefaultSqlSessionFactory类实现接口
5、编写SqlSessionFactoryBuilder类
6、测试SqlSession是否创建成功
九、编写执行器
1、编写BoundSql类用于封装sql语句
2、编写Executor接口,定义执行器方法
3、编写SimpleExecutor用于实现Executor接口
4、更新DefaultSqlSession类,实现selectList和selectOne方法
5、编写测试类进行测试
十、增加新增接口
(1)更改代码
(2)测试
十一、增加更新接口
(1)更新代码
(2)测试
十二、增加删除接口
(1)修改mapper文件及更改DefaultSqlSession类
(2)测试
十三、优化代码为按标签类型分
(1)bug点
(2)在MappedStatement类中增加sqlType属性
(3)封装时将标签名一同进行封装
(4)修改DefaultSqlSession类中的代码
(5)修改SimpleExecutor类中的方法
(6)测试
十四、优化代码(使框架支持Integer作为参数)
(1)修改SimpleExecutor类的代码
(2)测试
十五、总结
一、新建项目
1、新建空项目
2、命名
3、删除父项目src
二、新建模块
1、右键父项目,新建模块
2、命名
三、回顾JDBC
1、存在问题
- 数据库连接创建、释放频繁造成系统资源浪费,从而影响性能。
- sql语句存在硬编码,造成代码不易维护。
- 使用preparedStatement向占有位符号传参数存在硬编码问题。
- 对结果解析存在硬编码(查询列名),sql变化导致解析代码变化。
2、问题解决
- 数据库频繁创建连接以及释放资源:连接池
- sql语句及参数存在硬编码:配置文件XxxMapper.xml
- 手动解析封装返回结果集:反射、内省
1、创建测试类
package test;import org.junit.Test;public class TestJdbc {@Testpublic void testJdbc(){} }
2、创建测试数据库及表
3、创建实体类
package com.qingti.pojo;import lombok.Data;@Data @AllArgsConstructor @NoArgsConstructor public class User {private Integer id;private String username; }
4、导入mysql依赖
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.23</version></dependency>
5、编写查询jdbc
package test;import com.qingti.pojo.User;
import org.junit.Test;import java.sql.*;
import java.util.ArrayList;
import java.util.List;public class TestJdbc {@Testpublic void testJdbc(){Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;List<User> users = new ArrayList<User>();try {//加载JDBC驱动Class.forName("com.mysql.jdbc.Driver");//获取连接对象connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/handwrite?characterEncoding=utf-8&serverTimezone=Asia/Shanghai", "root", "123456");//获取preparedStatementpreparedStatement = connection.prepareStatement("select * from handwrite.mybatistable");//执行sql返回ResultSetresultSet = preparedStatement.executeQuery();//遍历数据,存入集合while (resultSet.next()){int id = resultSet.getInt("id");String username = resultSet.getString("username");User user = new User(id,username);users.add(user);}} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (SQLException e) {throw new RuntimeException(e);} finally {//关闭连接try {if(resultSet!=null){resultSet.close();}if (preparedStatement!=null){preparedStatement.close();}if (connection!=null){connection.close();}} catch (SQLException e) {throw new RuntimeException(e);} finally {}}System.out.println("查询到的数据是:"+users);}
}
6、运行测试
7、编写新增jdbc
@Testpublic void testInsert(){Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {//加载JDBC驱动Class.forName("com.mysql.jdbc.Driver");//获取连接对象connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/handwrite?characterEncoding=utf-8&serverTimezone=Asia/Shanghai", "root", "123456");//获取preparedStatementpreparedStatement = connection.prepareStatement("INSERT INTO handwrite.mybatistable values (null,?)");preparedStatement.setObject(1,"李四");//执行sqlint count = preparedStatement.executeUpdate();System.out.println(count>0?"新增成功":"新增失败");} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (SQLException e) {throw new RuntimeException(e);} finally {//关闭连接try {if(resultSet!=null){resultSet.close();}if (preparedStatement!=null){preparedStatement.close();}if (connection!=null){connection.close();}} catch (SQLException e) {throw new RuntimeException(e);} finally {}}}
8、运行测试
四、准备环境
1、编写UserMapper
public interface UserMapper {List<User> list(); }
2、编写resource/mapper
<mapper namespace="com.qingti.mapper.UserMapper"> <!-- 查询--><select id="list" resultType="com.qingti.pojo.User">select * from mybatistable</select> </mapper>
3、编写mybatis-config
<?xml version="1.0" encoding="UTF-8" ?> <configuration> <!--数据库配置信息--><dataSource><!--数据库的驱动地址--><property name="driverClass" value="com.mysql.cj.jdbc.Driver"/><!--连接字符串--><property name="jdbcUrl" value="jdbc:mysql://localhost:3306/handwrite?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource><!--存放mapper.xml的全路径--><mapper resource="mapper/UserMapper.xml"/> </configuration>
4、编写测试代码
public class TestMybatis {UserMapper userMapper;@Beforepublic void init(){//解析xmlString resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();//框架底层使用JDK动态代理给接口生成实现类对象userMapper = sqlSession.getMapper(UserDao.class);}@Testpublic void testList(){List<User> list = userMapper.list();for (User user : list) {System.out.println(user);}} }
五、使用dom4j解析xml文件
1、导入dom4j依赖
<dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.1</version></dependency>
2、在test目录创建测试xml
<?xml version="1.0" encoding="UTF-8" ?> <books><book id="1"><name>1</name><id>1</id></book><book id="2"><name>2</name><id>2</id></book><book id="3"><name>3</name><id>3</id></book> </books>
3、创建测试类
package test;import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.junit.Test;import java.util.List;public class TestBook {@Testpublic void test(){try {//创建一个解析器SAXReader saxReader = new SAXReader();//获取一个文档对象Document document = saxReader.read("D:\\selfstudy\\MybatisHandWrite\\hand-write-ssm\\hand-write-mybatis\\src\\test\\resources\\book.xml");//获取xml文件的根节点Element rootElement = document.getRootElement();System.out.println("根节点的名字是"+rootElement.getName());//获取子节点的集合List<Element> elements = rootElement.elements();System.out.println("子节点的个数为:"+elements.size());for (Element element : elements) {Attribute id = element.attribute("id");String value = id.getValue();System.out.println("id的值为:"+value);//book节点下的子节点集合List<Element> elements1 = element.elements();for (Element element1 : elements1) {//标签名String tagName = element1.getName();//标签内的内容String text = element1.getText();System.out.println(tagName+"="+text);}System.out.println("------------------------------");}} catch (DocumentException e) {throw new RuntimeException(e);} finally {}} }
4、运行测试
六、开始,编写Mapper解析API
1、自定义Resources类
Resources类的作用是获取一个类加载器,根据配置文件的路径,将配置文件加载成字节输入流存储在内存中。
创建Resources类
@Data public class Resources {/*** 根据路径将配置文件加载为字节流的形式,存储在内存中* @param path 配置文件的位置* @return 返回的字节流*/public static InputStream getResourceAsStream(String path) {//加载类路径下的配置文件,以字节流的形式返回 InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);return resourceAsStream;} }
2、定义Configuration类
对sql语句进行封装
/*** 对sql语句进行封装*/ @Data public class Configuration {/*** 数据源*/private DataSource dataSource;/*** 封装的mapper.xml文件中的sql语句,因为mapper中不止一条sql语句*/Map<String,MappedStatement> mappedStatementMap = new ConcurrentHashMap<>(); }
3、定义MappedStatement类
MappedStatement类作用是封装UserMapper.xml文件解析之后的SQL语句信息,在底层框架可以使用Dom4j进行解析
/*** 对sql语句进行解析*/ @Data @AllArgsConstructor @NoArgsConstructor public class MappedStatement {//id标识private String id;//sql语句返回值private String resultType;//参数类型private String parameterType;//sql语句private String sql; }
4、定义XmlMapperBuilder类
使用dom4j解析Mapper.xml配置文件
/*** 使用dom4j解析Mapper.xml配置文件*/ public class XmlMapperBuilder {/*** 配置数据封装对象*/private Configuration configuration;public XmlMapperBuilder(Configuration configuration) {this.configuration = configuration;}/*** 传入配置文件的字节流,解析配置文件,得到配置文件的封装* @param intputStream*/public void parse(InputStream intputStream) throws DocumentException {SAXReader saxReader = new SAXReader();//获取文档对象Document document = saxReader.read(intputStream);//获取根节点Element rootElement = document.getRootElement();//获取根节点的属性Attribute namespace = rootElement.attribute("namespace");//com.qingti.pojo.UserString namespaceValue = namespace.getValue();//xpath解析,解析xml配置文件,获取所有查询相关的节点List selectNodes = rootElement.selectNodes("//select");//xpath解析,解析xml配置文件,获取所有增加相关的节点List insertNodes = rootElement.selectNodes("//insert");//xpath解析,解析xml配置文件,获取所有修改相关的节点List updateNodes = rootElement.selectNodes("//update");//xpath解析,解析xml配置文件,获取所有删除相关的节点List deleteNodes = rootElement.selectNodes("//delete");List<Element> allNodes = new ArrayList<>();allNodes.addAll(selectNodes);allNodes.addAll(insertNodes);allNodes.addAll(updateNodes);allNodes.addAll(deleteNodes);for (Element element : allNodes) {//获取每条sql的id值String id = element.attributeValue("id");//获取返回值String resultType = element.attributeValue("resultType");//获取参数类型String parameterType = element.attributeValue("parameterType");//获取每个mappr节点中的sql语句String sqlText = element.getTextTrim();//封装对象MappedStatement mappedStatement = new MappedStatement();mappedStatement.setId(id);mappedStatement.setResultType(resultType);mappedStatement.setParameterType(parameterType);mappedStatement.setSql(sqlText);String key = namespaceValue+"."+id;configuration.getMappedStatementMap().put(key,mappedStatement);}}}
5、更新一下UserMapper.xml和UserMapper接口
(1)更新xml中的sql语句
<mapper namespace="com.qingti.mapper.UserMapper"> <!-- 查询--><select id="list" resultType="com.qingti.pojo.User">select * from mybatistable</select> <!-- id查--><select id="findById" resultType="com.qingti.pojo.User" parameterType="java.lang.Integer">select * from mybatistable where id=#{id}</select> <!-- 新增--><insert id="insert" resultType="java.lang.Integer" parameterType="com.qingti.pojo.User">insert into mybatistable values (null,#{username})</insert> <!-- 修改--><update id="update" resultType="java.lang.Integer" parameterType="com.qingti.pojo.User">update mybatistable set username=#{username} where id=#{id}</update> <!-- 删除--><delete id="delete" resultType="java.lang.Integer" parameterType="java.lang.Integer">delete from mybatistable where id=#{id}</delete> </mapper>
(2)更新方法接口
public interface UserMapper {List<User> list();User findById(Integer id);Integer add(User user);Integer update(User user);Integer delete(Integer id); }
6、测试解析结果
public class TestMapper {@Testpublic void test(){try {Configuration configuration = new Configuration();InputStream resourceAsStream = Resources.getResourceAsStream("mapper/UserMapper.xml");XmlMapperBuilder mapperBuilder = new XmlMapperBuilder(configuration);mapperBuilder.parse(resourceAsStream);System.out.println(configuration);} catch (DocumentException e) {throw new RuntimeException(e);}} }
七、解析config.xml文件
1、导入连接池依赖
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version></dependency>
2、定义XmlConfigBuilder类
public class XmlConfigBuilder {private Configuration configuration;public XmlConfigBuilder(Configuration configuration) {this.configuration = configuration;}public Configuration parseMyBatisXConfig(InputStream inputStream) throws DocumentException {SAXReader saxReader = new SAXReader();Document document = saxReader.read(inputStream);//根节点 configurationElement rootElement = document.getRootElement();List<Element> property = rootElement.selectNodes("//property");Properties properties = new Properties();for (Element element : property) {String name = element.attributeValue("name");String value = element.attributeValue("value");properties.setProperty(name,value);}//初始化数据库连接池DruidDataSource druidDataSource = new DruidDataSource();druidDataSource.setDriverClassName(properties.getProperty("driverClass"));druidDataSource.setUrl(properties.getProperty("jdbcUrl"));druidDataSource.setUsername(properties.getProperty("username"));druidDataSource.setPassword(properties.getProperty("password"));//设置数据库数据源configuration.setDataSource(druidDataSource);//Mybatis的核心配置文件,映射Mapper.xml文件List<Element> list = rootElement.selectNodes("//mapper");for (Element element : list) {String resource = element.attributeValue("resource");InputStream resourceAsStream = Resources.getResourceAsStream(resource);XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);xmlMapperBuilder.parse(resourceAsStream);}return configuration;} }
3、测试
public class TestXml {@Testpublic void test() throws DocumentException {Configuration configuration = new Configuration();XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");xmlConfigBuilder.parseMyBatisXConfig(resourceAsStream);System.out.println("解析完毕!");System.out.println(configuration);} }
现在我们可以得到xml文件中的数据了,于是我们完成了第一步,加载配置文件
八、获取SQLSession
1、编写SqlSession接口
SqlSession是提供与数据库交互的各种方法的接口
public interface SqlSession {/***查询所有数据* @param statementId sql语句唯一ID* @param params 查询sql语句所需参数,可变参数* @param <T>*/<T> List<T> selectList(String statementId, Object... params)throws Exception;/***按条件查询单个对象* @param statementId sql语句唯一ID* @param params 查询sql语句所需参数,可变参数* @param <T>*/<T> T selectOne(String statementId, Object... params)throws Exception;/***新增* @param statementId sql语句唯一ID* @param params 查询sql语句所需参数,可变参数* @param <T>*/<T> T insert(String statementId, Object... params)throws Exception;/***更新* @param statementId sql语句唯一ID* @param params 查询sql语句所需参数,可变参数* @param <T>*/<T> T update(String statementId, Object... params)throws Exception;/***删除* @param statementId sql语句唯一ID* @param params 查询sql语句所需参数,可变参数* @param <T>*/<T> T delete(String statementId, Object... params)throws Exception;/*** 为Mapper层的接口JDK动态代理生成实现类* @param mapperClass 字节码* @return 接口的代理类对象* @param <T> 反向* @throws Exception*/<T> T getMapper(Class<?> mapperClass)throws Exception; }
2、编写SqlSessionFactory接口
SqlSessionFactory是MyBatis的关键对象,它是单个数据库映射关系经过编译后的内存镜像
public interface SqlSessionFactory {//获取sqlSessionSqlSession openSession(); }
3、编写DefaultSqlSession类实现接口
(1)先创建一个Command类来区分sql语句
public enum CommandType {INSERT,UPDATE,DELETE }
(2)编写实现类
public class DefaultSqlSession implements SqlSession{//封装的配置信息private Configuration configuration;public DefaultSqlSession(Configuration configuration) {this.configuration = configuration;}@Overridepublic <T> List<T> selectList(String statementId, Object... params) throws Exception {return null;}@Overridepublic <T> T selectOne(String statementId, Object... params) throws Exception {return null;}@Overridepublic <T> T insert(String statementId, Object... params) throws Exception {return null;}@Overridepublic <T> T update(String statementId, Object... params) throws Exception {return null;}@Overridepublic <T> T delete(String statementId, Object... params) throws Exception {return null;}/*** 为Mapper层的接口JDK动态代理生成实现类* @param mapperClass 字节码* @return 接口的代理类对象* @param <T> 反向* @throws Exception*/@Overridepublic <T> T getMapper(Class<?> mapperClass) throws Exception {Object instance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//接口中的方法名String methodName = method.getName();//接口的全类名String className = method.getDeclaringClass().getName();//拼接Sql的唯一标识String statementId = className + "." + methodName;//获取方法被调用后的返回值类型Type genericReturnType = method.getGenericReturnType();if(methodName.contains(CommandType.INSERT.toString())){return insert(statementId,args);} else if (methodName.contains(CommandType.DELETE.toString())) {return delete(statementId,args);} else if (methodName.contains(CommandType.UPDATE.toString())) {return update(statementId,args);}//判断是否进行了泛型类型的参数化(判断返回值类型是否是泛型)if (genericReturnType instanceof ParameterizedType){List<Object> objects = selectList(statementId, args);return objects;}else{return selectOne(statementId, args);}}});return (T)instance;} }
4、编写DefaultSqlSessionFactory类实现接口
public class DefaultSqlSessionFactory implements SqlSessionFactory{private Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration){this.configuration = configuration;}@Overridepublic SqlSession openSession() {return new DefaultSqlSession(configuration);} }
5、编写SqlSessionFactoryBuilder类
该类用于生成SqlSessionFactory对象
public class SqlSessionFactoryBuilder {public SqlSessionFactory build(InputStream inputStream) throws DocumentException {//获取configuration对象Configuration configuration = new Configuration();XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);xmlConfigBuilder.parseMyBatisXConfig(inputStream);//创建SqlSessionFactoryDefaultSqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);return sqlSessionFactory;} }
6、测试SqlSession是否创建成功
找到TestMysbatis并进行测试
九、编写执行器
1、编写BoundSql类用于封装sql语句
@Data @NoArgsConstructor @AllArgsConstructor public class BoundSql {//要执行的sql语句private String sqlText;//执行sql的参数集合private List<String> parameterMappingList = new ArrayList<>();}
2、编写Executor接口,定义执行器方法
/*** sql语句执行器*/ public interface Executor {<T>List<T> query(Configuration configuration, MappedStatement mappedStatement,Object... params) throws SQLException, ClassNotFoundException, Exception; }
3、编写SimpleExecutor用于实现Executor接口
主要包含:
- Sql语句的转换
- Sql的执行
- 返回值的封装
/*** sql语句的执行器*/
public class SimpleExecutor implements Executor{@Overridepublic <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {//1、获取数据库连接Connection connection = configuration.getDataSource().getConnection();//2、获取要执行的sql语句String sql = mappedStatement.getSql();//拿到配置文件中的原始sql语句//转换sql语句,把#{}转换为?BoundSql boundSql = this.getBoundSql(sql);//获取PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());//设置参数:com.qingti.pojo.UserString parameterType = mappedStatement.getParameterType();Class<?> classType = this.getClassType(parameterType);//获取sql语句参数集合List<String> parameterMappingList = boundSql.getParameterMappingList();for (int i = 0;i < parameterMappingList.size();i++) {String content = parameterMappingList.get(i);//反射Field declaredField = classType.getDeclaredField(content);declaredField.setAccessible(true);//取出参数Object data = declaredField.get(params[0]);preparedStatement.setObject(i+1,data);}//执行SqLString id = mappedStatement.getId();ResultSet resultSet = null;if(id.contains(CommandType.DELETE.toString())||id.contains(CommandType.INSERT.toString())||id.contains(CommandType.UPDATE.toString())){//增删改Integer result = preparedStatement.executeUpdate();ArrayList<Integer> resultList = new ArrayList<>();resultList.add(result);return (List<T>)resultList;}else {//查询resultSet = preparedStatement.executeQuery();}//获取返回值的类型String resultType = mappedStatement.getResultType();Class<?> returnTypeClass = this.getClassType(resultType);List<Object> objects = new ArrayList<>();while (resultSet.next()){//调无参构造方法生成对象Object instance = returnTypeClass.newInstance();ResultSetMetaData metaData = resultSet.getMetaData();for (int i = 1; i <= metaData.getColumnCount(); i++) {//字段名字String columnName = metaData.getColumnName(i);//获取值Object value = resultSet.getObject(columnName);//属性封装//使用反射根据数据库表和实体类的属性和字段对应关系数据封装PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,returnTypeClass);Method writeMethod = propertyDescriptor.getWriteMethod();writeMethod.invoke(instance,value);}objects.add(instance);}return (List<T>)objects;}Map<Integer,Integer> map = new TreeMap<Integer,Integer>();int findPosition = 0;List<String> parameterMappings = new ArrayList<>();/*** 根据类的全名称获取Class* @param parameterType* @return* @throws ClassNotFoundException*/public Class<?> getClassType(String parameterType) throws ClassNotFoundException {if(parameterType!=null){Class<?> aClass = Class.forName(parameterType);return aClass;}return null;}/*** 转换API* 1、将#{}使用?代替* 2、解析出#{}内的值进行存储* @param sql* @return*/private BoundSql getBoundSql(String sql){//完成sql语句解析工作this.parserSql(sql);Set<Map.Entry<Integer,Integer>> entries = map.entrySet();for (Map.Entry<Integer, Integer> entry : entries) {Integer key = entry.getKey()+2;Integer value = entry.getValue();parameterMappings.add(sql.substring(key,value));}for (String s : parameterMappings) {sql = sql.replace("#{"+s+"}","?");}BoundSql boundSql = new BoundSql(sql, parameterMappings);return boundSql;}private void parserSql(String sql){int openIndex = sql.indexOf("#{",findPosition);if (openIndex != -1){int endIndex = sql.indexOf("}",findPosition+1);if(endIndex != -1){map.put(openIndex,endIndex);findPosition = endIndex+1;parserSql(sql);//递归检查#{}}else{System.out.println("SQL语句中参数错误..");}}}}
4、更新DefaultSqlSession类,实现selectList和selectOne方法
使用Executor对象完成数据库的查询
@Overridepublic <T> List<T> selectList(String statementId, Object... params) throws Exception {//将SimpleExecutorQuery方法完成查询SimpleExecutor simpleExecutor = new SimpleExecutor();MappedStatement mappedStatement = this.configuration.getMappedStatementMap().get(statementId);List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);return (List<T>)list;}@Overridepublic <T> T selectOne(String statementId, Object... params) throws Exception {List<Object> objects = this.selectList(statementId, params);if ((objects.size()==1)){return (T) objects.get(0);}else if(objects.size()>1){throw new RuntimeException("查询结果为空或者查询结果不唯一!");}else{throw new RuntimeException("查询结果为空!");}}
5、编写测试类进行测试
public class TestMybatis {UserMapper userMapper;@Beforepublic void init(){try {//解析xmlString resource = "mybatis-config.xml";//加载配置文件,并得到字节流InputStream inputStream = Resources.getResourceAsStream(resource);//创建线程工厂SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();System.out.println(sqlSession);//框架底层使用JDK动态代理给接口生成实现类对象userMapper = sqlSession.getMapper(UserMapper.class);} catch (Exception e) {throw new RuntimeException(e);}}@Testpublic void testList(){List<User> list = userMapper.list();for (User user : list) {System.out.println(user);}}@Testpublic void testFindById(){User user = userMapper.findById(1);System.out.println(user);}
}
(1)执行后发现报错
(2)debug进行错误定位
(3)发现是因为程序想在Integer类中找到id属性时出错,于是更改findById的传入值为User
(4)重新测试
@Testpublic void testFindById(){User u = new User();u.setId(1);User user = userMapper.findById(u);System.out.println(user);}
十、增加新增接口
(1)更改代码
@Overridepublic <T> T insert(String statementId, Object... params) throws Exception {SimpleExecutor simpleExecutor = new SimpleExecutor();MappedStatement mappedStatement = this.configuration.getMappedStatementMap().get(statementId);List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);if (list.size()>0){return (T) list.get(0);}else {return (T) "0";}}
(2)测试
@Testpublic void testInsert(){User user = new User();user.setUsername("qingti");int insert = userMapper.insert(user);System.out.println(insert>0?"新增成功":"新增失败");}
十一、增加更新接口
(1)更新代码
@Overridepublic <T> T update(String statementId, Object... params) throws Exception {SimpleExecutor simpleExecutor = new SimpleExecutor();MappedStatement mappedStatement = this.configuration.getMappedStatementMap().get(statementId);List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);if (list.size()>0){return (T) list.get(0);}else {return (T) "0";}}
(2)测试
@Testpublic void testUpdate(){User user = new User(6,"kunkun");int update = userMapper.update(user);System.out.println(update>0?"修改成功":"修改失败");}
十二、增加删除接口
(1)修改mapper文件及更改DefaultSqlSession类
@Overridepublic <T> T delete(String statementId, Object... params) throws Exception {SimpleExecutor simpleExecutor = new SimpleExecutor();MappedStatement mappedStatement = this.configuration.getMappedStatementMap().get(statementId);List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);if (list.size()>0){return (T) list.get(0);}else {return (T) "0";}}
(2)测试
@Testpublic void testDelete(){User user = new User();user.setId(6);int delete = userMapper.delete(user);System.out.println(delete>0?"删除成功":"删除失败");}
十三、优化代码为按标签类型分
(1)bug点
在DefaultSqlSession类的getMapper方法中
这种写法只是简单按照mapper中的方法名来判断用的是哪种sql语句(只能识别insert、update、delete、select)
(2)在MappedStatement类中增加sqlType属性
(3)封装时将标签名一同进行封装
(4)修改DefaultSqlSession类中的代码
@Overridepublic <T> T getMapper(Class<?> mapperClass) throws Exception {Object instance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//接口中的方法名String methodName = method.getName();//接口的全类名String className = method.getDeclaringClass().getName();//拼接Sql的唯一标识String statementId = className + "." + methodName;//获取方法被调用后的返回值类型Type genericReturnType = method.getGenericReturnType();Map<String, MappedStatement> mappedStatementMap = configuration.getMappedStatementMap();MappedStatement mappedStatement = mappedStatementMap.get(statementId);if("insert".equals(mappedStatement.getSqlType())){return insert(statementId,args);} else if ("delete".equals(mappedStatement.getSqlType())) {return delete(statementId,args);} else if ("update".equals(mappedStatement.getSqlType())) {return update(statementId,args);}//判断是否进行了泛型类型的参数化(判断返回值类型是否是泛型)if (genericReturnType instanceof ParameterizedType){List<Object> objects = selectList(statementId, args);return objects;}else{return selectOne(statementId, args);}}});return (T)instance;}
(5)修改SimpleExecutor类中的方法
(6)测试
此时,我们将insert的方法名改为add
修改测试方法为add
十四、优化代码(使框架支持Integer作为参数)
(1)修改SimpleExecutor类的代码
for (int i = 0;i < parameterMappingList.size();i++) {String content = parameterMappingList.get(i);//若不是Integer,则进行反射;否则直接传入参数if (!"java.lang.Integer".equals(parameterType)){//反射Field declaredField = classType.getDeclaredField(content);declaredField.setAccessible(true);//取出参数Object data = declaredField.get(params[0]);preparedStatement.setObject(i+1,data);}else {preparedStatement.setObject(i+1,params[0]);}}