记一次手动实现mybatis框架findAll功能
Mybatis是基于持久层开发的框架,它内部封装了JDBC,开发者不需要将精力放在数据库连接这方面,只需要把关注点放在sql语句的编写上。Mybatis通过xml或者注解的方式将要执行的各种statement对象配置起来,并且通过java对象和statement中的sql的动态参数进行映射生成sql语句。最后由Mybatis框架执行sql语句并将结果集对象通过反射技术封装成javabean对象返回。Mybatis采用ORM思想(object relational mapping)将java实体和数据库映射结合。框架内部封装了jdbc底层技术。开发前准备 导入依赖jar包
mysql
mysql-connector-java
5.1.36
dom4j
dom4j
1.6.1
jaxen
jaxen
1.1.3
junit
junit
4.12
表结构
+----------+--------------+------+-----+---------+-------+
| Field| Type| Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| username | varchar(255) | YES|| NULL||
| password | varchar(255) | YES|| NULL||
+----------+--------------+------+-----+---------+-------+
javaBean
public class User{
private String username;
private String password;
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}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;
}
}
接口Mapper和Mapper文件准备
package mapper;
import domain.User;
import java.util.List;
public interface UserMapper {
/**
* 查询所有用户的方法
* @return
*/
List findAll();
}
SELECT * FROMuser
编写测试类
import domain.User;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class CustomerMyBatisTest {
@Test
public void testFindAll() {
//1.将主配置文件转换成字节输入流
SqlSession sqlSession = null;
InputStream is = null;
try {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
System.out.println(is);
//2.创建一个SqlSessionFactoryBuilder的对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.构建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = builder.build(is);
//使用了构建者模式
//4.创建SqlSession对象
sqlSession = sqlSessionFactory.openSession();
//5.通过SqlSession对象,创建UserMapper接口的代理对象----->动态代理
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//6.调用mapper对象的findAll()方法
List users = mapper.findAll();
for (User user : users) {
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//7.关闭资源
sqlSession.close();
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Mybatis配置文件
测试类中的所有接口类都是空实现,需要我们自己补充。
项目结构如下
文章图片
SqlSessionFactory接口及其实现类DefaultSqlSessionFactory
SqlSession接口及其实现类DefalutSqlSession
sqlMapConfig Mybatis配置文件
Resources类负责读取类路径下资源文件,具体内容如下:
public class Resources {
public static InputStream getResourceAsStream(String path){
InputStream is = Resources.class.getClassLoader().getResourceAsStream(path);
return is;
}
}
项目准备完毕,接下来就是coding。。
读取sqlMapConfig获取jdbc连接
工具类XmlConfigBuilder
1、解析获取dateSource连接必须数据
public class XmlConfigBuilder {
//加载配置文件,解析相关信息,封装到Configuration对象返回
public static Configuration loadSqlMapConfig(InputStream is) {
SAXReader Reader = new SAXReader();
//创建SAXReader对象解析XML
try {
Document document = Reader.read(is);
//获取document对象
List propertyElement = document.selectNodes("//property");
//通过Xpath技术解析property对象
解析以后可以获取Element元素集合,对应的name分别是:XML中配置的username,password,url、以及dirver
创建Configuration实体类负责接收
public class Configuration {
private String username;
private String password;
private String driver;
private String url;
//存放每个mapper文件对应的sql语句和resultType
private Map mappers = new HashMap();
Get/Set....
}
Configuration cfg = new Configuration();
//创建Configuration对象
//遍历集合取出对应value
for (Element element : propertyElement) {
String name = element.attributeValue("name");
String value = https://www.it610.com/article/element.attributeValue("value");
if ("username".equals(name)) {
cfg.setUsername(value);
}
if ("password".equals(name)) {
cfg.setPassword(value);
}
if ("url".equals(name)) {
cfg.setUrl(value);
}
if ("driver".equals(name)) {
cfg.setDirver(value);
}
2、解析获取映射文件路径
for (Element mapperElement : mapperElements) {
String resource = mapperElement.attributeValue("resource");
//获取映射文件路径
Map mappers = loadMapper(resource);
//通过配置文件加载每个映射文件,具体方法后面实现TODO
cfg.setMappers(mappers);
//将获取的每一个mapper对象放入Configuration对象中存储,并且补充
}
}
return cfg;
} catch (DocumentException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
3、加载每个映射文件获取对应sql语句、resultType
//加载映射文件获取对应sql语句、resultType封装Mapper对象集合返回Map
private static Map loadMapper(String resource) {
Map mappers = new HashMap();
InputStream is = Resources.getResourceAsStream(resource);
//通过Resources对象将Mapper.xml文件转为流
SAXReader reader = new SAXReader();
//创建SAXReader对象
try {
Document document = reader.read(is);
//获取document对象
Element rootElement = document.getRootElement();
//获得Mapper.xml文件根标签及标签
String namespace = rootElement.attributeValue("namespace");
//获取namespace
List selectElements = document.selectNodes("//select");
//获取select标签集合
//遍历集合取出每一个select标签的id和resultType
for (Element selectElement : selectElements) {
String id = selectElement.attributeValue("id");
String resultType = selectElement.attributeValue("resultType");
String sql = selectElement.getTextTrim();
//获取标签中的内容及内部SQL语句
//封装mapper对象
Mapper mapper = new Mapper();
mapper.setSql(sql);
mapper.setResultType(resultType);
mappers.put(namespace + "." + id, mapper);
//存入map集合}
return mappers;
} catch (DocumentException e) {
e.printStackTrace();
throw new RuntimeException();
}}
4、sql语句返回值类型,及其对应的连接信息全部封装在Configuration对象中,补充Configuration对象中连接方法
public Connection getConnection() throws SQLException {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
}
SqlSessionFactory、SqlSession具体实现类方法实现
使用过mybatis就会发现,基本上所有的增删改查操作的执行都是交给SqlSession对象实现的。(sqlSession.getMapper())
也就是说获取连接也是SqlSession内部实现的,所有我们需要将Configuration对象给SqlSession实现类DefaultSqlSession类。
还需要解决几个问题
1)到底什么时候加载配置文件?
我们可以在在SqlSessionFactory的openSqlSession方法调用时加载配置文件
2)通过什么方式通知程序读取Mybatis配置文件(SqlMapConfig.xml)。
我们已经将配置文件转为流了,只需要在SqlSessionFactory调用build方法的时候将流传入即可,所以我们只需要将流传给SqlSessionFactory的实现类DefaultSqlSessionFacotory。
首先实现SqlSessionFactoryBuilder类
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream is) {
DefaultSqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory();
//创建SqlSessionFactory对象设置上流并返回
sqlSessionFactory.setIs(is);
return sqlSessionFactory;
}
}
DefaultSqlSessionFactory类实现
public class DefaultSqlSessionFactory implements SqlSessionFactory{
private InputStream is;
//在openSession方法内部创建SqlSession对象
public SqlSession openSession() {
//创建SqlSession对象
DefaultSqlSession sqlSession = new DefaultSqlSession();
//通过loadSqlMapConfig方法
Configuration cfg = XmlConfigBuilder.loadSqlMapConfig(is);
//给SqlSession对象设置Configuration对象
sqlSession.setCfg(cfg);
return sqlSession;
}
public void setIs(InputStream is) {
this.is = is;
}
}
DefalutSqlSession类实现
1、编写selectList()方法
//key:mapper.xml中select的方法的全限定名(namespace.id名)
public List selectList(String key) {
//获取连接对象
Connection conn = null;
try {
conn = cfg.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
//获取SQl语句
Map mappers = cfg.getMappers();
Mapper mapper = mappers.get(key);
String sql = mapper.getSql();
//获取要执行的SQL语句String resultType = mapper.getResultType();
//JavaBean的全限定名
//预编译SQL语句
try {
PreparedStatement pstm = conn.prepareStatement(sql);
//执行Sql语句
ResultSet resultSet = pstm.executeQuery();
//通过反射技术将结果封装成javaBean对象,具体实现后面总结
List list = Converser.converList(resultSet, Class.forName(resultType));
return list;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}finally {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2、通过动态代理获取代理对象
public E getMapper(Class mapperClass) {
return (E) Proxy.newProxyInstance(mapperClass.getClassLoader(),new Class[]{mapperClass},new MapperProxyFactory(this));
}
Proxy的newProxyInstance方法中需要三个参数:classLoader类加载器,代理对象实现的接口的字节码对象也就是我们这里的传入的mapperClass对象,InvocationHandler接口的实现类对象。具体实现如下
public class MapperProxyFactory implements InvocationHandler{
private SqlSession sqlSession;
public MapperProxyFactory(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果返回值是一个List集合才执行如下代码,即findAll
Class> returnType = method.getReturnType();
if (returnType == List.class) {
String methodName = method.getName();
//获取方法名Class> clazz = method.getDeclaringClass();
//获取接口字节码对象
String className = clazz.getName();
//获取接口的全限定名
//key :mapper接口的全限定名
String key = className+"."+methodName;
List
【记一次手动实现mybatis框架findAll功能】3、Converser类的具体实现(反射将resultType映射成javaBean对象)
public static List converList(ResultSet set, Class clazz){
List javaBeans = new ArrayList();
//1.遍历结果集
try {
//根据结果集元数据,获取结果集中的每一列的列名
ResultSetMetaData metaData = https://www.it610.com/article/set.getMetaData();
int columnCount = metaData.getColumnCount();
//获取总列数
while (set.next()){
//每次遍历,遍历出一条数据,每条数据就对应一个JavaBean对象
E o = (E) clazz.newInstance();
//根据列名获取每一列数据
for(int i=1;
i<=columnCount;
i++){
String columnName = metaData.getColumnName(i);
//获取列名
Object value = set.getObject(columnName);
//获取该列的值
//将该列的值存放到JavaBean中
//也就是调用JavaBean的set方法,使用内省机制
PropertyDescriptor descriptor = new PropertyDescriptor(columnName,clazz);
Method writeMethod = descriptor.getWriteMethod();
//获取该属性的set方法
//调用set方法
writeMethod.invoke(o,value);
}
javaBeans.add(o);
}
} catch (Exception e) {
e.printStackTrace();
}
return javaBeans;
}
推荐阅读
- EffectiveObjective-C2.0|EffectiveObjective-C2.0 笔记 - 第二部分
- 野营记-第五章|野营记-第五章 讨伐梦魇兽
- 20170612时间和注意力开销记录
- 2018年11月19日|2018年11月19日 星期一 亲子日记第144篇
- 叙述作文
- 2019年12月24日
- 【故障公告】周五下午的一次突发故障
- 人生感悟记#环境仪器宋庆国成长记#072
- 2019.4.18感恩日记
- 我要我们在一起(二)