首先我们来回顾下MyBatis完成一个简单查询的步骤:
1.创建maven工程,导入相关依赖的jar包
2.创建实体类User
3.创建接口UserDao,定义方法findAll()
4.配置UserDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.UserDao">
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
</mapper>
5.配置核心配置文件SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="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:///db1"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/itheima/dao/UserDao.xml"/>
</mappers>
</configuration>
6.测试类
public class Test {
public static void main(String[] args) throws IOException {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(Resources.getResourceAsReader("SqlMapConfig.xml"));
SqlSession session = factory.openSession();
UserDao userDao = session.getMapper(UserDao.class);
List<User> list = userDao.findAll();
for (User user : list) {
System.out.println(user);
}
session.close();
}
}
下面来自定义一个Mybatis框架完成这个最基本的功能(这里先写原理,源代码最后附上)
1.Resources:用于得到xml配置文件的输入流
public static InputStream getResourceAsStream(String filePath){
return Resources.class.getClassLoader().getResourceAsStream(filePath);
}
2.Mapper:存储sql语句和返回值类型(一个方法的mapper)
private String queryString;//SQL
private String resultType;//实体类的全限定类名
3.Configuration:存储sql连接的配置信息和mappers(所有方法的mapper,键值对中键为"全限定类名.方法名")
private String driver;
private String url;
private String username;
private String password;
private Map<String,Mapper> mappers = new HashMap<String,Mapper>();
4.SqlSessionFactoryBuilder:
利用配置文件的输入流创建sqlSessionFactory(XMLConfigBuilder工具类用于将配置文件信息加载至configuration对象),将configuration传递至sqlSessionFactory
public SqlSessionFactory build(InputStream config){
Configuration cfg = XMLConfigBuilder.loadConfiguration(config);
return new DefaultSqlSessionFactory(cfg);
}
5.SqlSessionFactory:创建sqlSession,将从sqlSessionFactoryFactoryBuilder传递过来的configuration传递至sqlSession
private Configuration cfg;
public DefaultSqlSessionFactory(Configuration cfg){
this.cfg = cfg;
}
public SqlSession openSession() {
return new DefaultSqlSession(cfg);
}
6.SqlSession:通过传递过来的Configuration,得到一个数据库的连接connection(用到了DataSourceUtil)和所有mapper类mappers,创建 一个代理类并将解析出来的connection和mappers传递给代理类MapperProxy
private Configuration cfg;
private Connection connection;
public DefaultSqlSession(Configuration cfg){
this.cfg = cfg;
connection = DataSourceUtil.getConnection(cfg);
}
public <T> T getMapper(Class<T> daoInterfaceClass) {
return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),
new Class[]{daoInterfaceClass},new MapperProxy(cfg.getMappers(),connection));
}
7.MapperProxy:判断是否有执行的方法(如调用findAll方法便由它判断configuration中是否存储了相应的键值对),没有则抛出异常(这种情况就是配置文件未配置相应方法和它对应的sql语句),若有则将对应的mapper和conn交给Executor去执行sql操作
private Map<String,Mapper> mappers;
private Connection conn;
public MapperProxy(Map<String,Mapper> mappers,Connection conn){
this.mappers = mappers;
this.conn = conn;
}
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.获取mappers中的Mapper对象
Mapper mapper = mappers.get(key);
//5.判断是否有mapper
if(mapper == null){
throw new IllegalArgumentException("传入的参数有误");
}
//6.调用工具类执行查询所有
return new Executor().selectList(mapper,conn);
}
8.Executor拿到了存储sql语句和返回值类型的mapper对象和conn后,就可以执行sql语句了,使用java内省机制(借助PropertyDescriptor)实现对返回值对象属性的封装
public <E> List<E> 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);
//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();
//取出结果集的元信息: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);//要求:实体类的属性和数据库表的列名保持一种
//获取它的写入方法(setter方法)
Method writeMethod = pd.getWriteMethod();
//把获取的列的值,给对象赋值
writeMethod.invoke(obj,columnValue);
}
//把赋好值的对象加入到集合中
list.add(obj);
}
return list;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
release(pstm,rs);
}
}
至此,自定义框架mybatis封装完毕,运行test测试类可取出数据库数据
完整源代码下载链接: https://pan.baidu.com/s/1n7wX3nmtB1ecmIn_PRrlGw 密码: 9fc1