目录
一、新建项目
二、新建模块
三、回顾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 { @Test public 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 {
@Test
public 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");
//获取preparedStatement
preparedStatement = connection.prepareStatement("select * from handwrite.mybatistable");
//执行sql返回ResultSet
resultSet = 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
@Test
public 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");
//获取preparedStatement
preparedStatement = connection.prepareStatement("INSERT INTO handwrite.mybatistable values (null,?)");
preparedStatement.setObject(1,"李四");
//执行sql
int 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; @Before public void init(){ //解析xml String 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); } @Test public 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 { @Test public 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.User String 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 { @Test public 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); //根节点 configuration Element 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 { @Test public 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 { //获取sqlSession SqlSession 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; } @Override public <T> List<T> selectList(String statementId, Object... params) throws Exception { return null; } @Override public <T> T selectOne(String statementId, Object... params) throws Exception { return null; } @Override public <T> T insert(String statementId, Object... params) throws Exception { return null; } @Override public <T> T update(String statementId, Object... params) throws Exception { return null; } @Override public <T> T delete(String statementId, Object... params) throws Exception { return null; } /** * 为Mapper层的接口JDK动态代理生成实现类 * @param mapperClass 字节码 * @return 接口的代理类对象 * @param <T> 反向 * @throws Exception */ @Override public <T> T getMapper(Class<?> mapperClass) throws Exception { Object instance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() { @Override public 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; } @Override public 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); //创建SqlSessionFactory DefaultSqlSessionFactory 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{
@Override
public <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.User
String 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);
}
//执行SqL
String 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对象完成数据库的查询
@Override public <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; } @Override public <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;
@Before
public void init(){
try {
//解析xml
String 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);
}
}
@Test
public void testList(){
List<User> list = userMapper.list();
for (User user : list) {
System.out.println(user);
}
}
@Test
public void testFindById(){
User user = userMapper.findById(1);
System.out.println(user);
}
}
(1)执行后发现报错
(2)debug进行错误定位
(3)发现是因为程序想在Integer类中找到id属性时出错,于是更改findById的传入值为User
(4)重新测试
@Test public void testFindById(){ User u = new User(); u.setId(1); User user = userMapper.findById(u); System.out.println(user); }
十、增加新增接口
(1)更改代码
@Override public <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)测试
@Test
public void testInsert(){
User user = new User();
user.setUsername("qingti");
int insert = userMapper.insert(user);
System.out.println(insert>0?"新增成功":"新增失败");
}
十一、增加更新接口
(1)更新代码
@Override public <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)测试
@Test
public void testUpdate(){
User user = new User(6,"kunkun");
int update = userMapper.update(user);
System.out.println(update>0?"修改成功":"修改失败");
}
十二、增加删除接口
(1)修改mapper文件及更改DefaultSqlSession类
@Override public <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)测试
@Test public 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类中的代码
@Override
public <T> T getMapper(Class<?> mapperClass) throws Exception {
Object instance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public 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]);
}
}