大家都知道MyBatis是我们常用的一个持久层框架,那么今天我们就来深入Mybatis底层,来探究MyBatis到底是怎么实现数据库操作的呢?
1 传统的开发模式
接口定义业务方法
public interface UserService {
public User getUserById(int id);
}
实现类实现接口方法 (sql语句写在java代码里面)
public class UserServiceImpl implements UserService{
@Override
public User getUserById(int id) {
Connection conn = JDBCTools.getConnection();
String sql = "select * from user where id = ?";
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, id);
rs = pstmt.executeQuery();
if(rs.next()){
int sid = rs.getInt(1);
String name = rs.getString(2);
User user = new User(sid,name);
return user;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCTools.release(conn, pstmt, rs);
}
return null;
}
}
测试
@Test
public void f1() throws Exception{
UserService userService = new UserServiceImpl();
User user = userService.getUserById(1);
System.out.println(user);
}
2 Mybatis的开发模式
定义接口方法
public interface UserMapper {
public User getUserById(int id);
}
接口对应的映射xml文件(sql语句写在xml文件)
<?xml version="1.0" encoding="UTF-8" ?>
测试
@Test
public void f1() throws Exception{
UserMapper mapper = (UserMapper) new MyInvocationHandler().getInstance(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
}
通过以上代码可以看到,MyBatis的方式省去了实现类的创建,改为用xml来定义业务方法的具体实现
为什么MyBatis可以不用通过实例化对象来操作我们的代码呢?这里就不得不说说jdk的动态代理
jdk动态代理运行时结合接口和mapper.xml来动态创建一个代理对象,程序调用该代理对象的方法来完成业务
创建一个类,实现InvocationHandler接口
1 自定义getInstance方法:入参为目标对象,通过Proxy.newProxyInstance方法创建代理对象,并返回
2 实现接口的invoke方法,通过反射机制完成业务逻辑代码
invoke方法是核心代码,在该方法中实现具体的业务需求
3 使用invoke方法解析数据库信息配置xml,创建数据库连接对象
//读取数据源配置信息
public static Map getProperties(){
Map map = new HashMap();
SAXReader reader = new SAXReader();
try {
Document document = reader.read("src/config.xml");
//获取根节点
Element root = document.getRootElement();
Iterator iter = root.elementIterator();
while(iter.hasNext()){
Element e = (Element) iter.next();
//解析environments节点
if("environments".equals(e.getName())){
Iterator iter2 = e.elementIterator();
while(iter2.hasNext()){
//解析environment节点
Element e2 = (Element) iter2.next();
Iterator iter3 = e2.elementIterator();
while(iter3.hasNext()){
Element e3 = (Element) iter3.next();
//解析dataSource节点
if("dataSource".equals(e3.getName())){
if("POOLED".equals(e3.attributeValue("type"))){
Iterator iter4 = e3.elementIterator();
//获取数据库连接信息
while(iter4.hasNext()){
Element e4 = (Element) iter4.next();
map.put(e4.attributeValue("name"),e4.attributeValue("value"));
}
}
}
}
}
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return map;
}
//获取信息,创建数据源对象
Map map = ParseXML.getProperties();
ComboPooledDataSource datasource = new ComboPooledDataSource();
datasource.setDriverClass(map.get("driver"));
datasource.setJdbcUrl(map.get("url"));
datasource.setUser(map.get("username"));
datasource.setPassword(map.get("password"));
datasource.setInitialPoolSize(20);
datasource.setMaxPoolSize(40);
datasource.setMinPoolSize(2);
datasource.setAcquireIncrement(5);
Connection conn = datasource.getConnection();
数据库连接,接下来就需要获取待执行的SQL语句,sql的定义全部写在UserMapper.xml中;解析xml执行sql语句,执行完毕,查询结果会保存在ResultSet中,还需要将ResultSet对象中的数据进行解析,封装到JavaBean中返回
上述步骤通过两步实现
第一步:反射机制创建User对象
第二步:通过反射动态执行类中所有属性的setter方法,完成赋值
//获取sql语句
String sql = element.getText();
//获取参数类型
String parameterType = element.attributeValue("parameterType");
//创建pstmt
PreparedStatement pstmt = createPstmt(sql,parameterType,conn,args);
ResultSet rs = pstmt.executeQuery();
if(rs.next()){
//读取返回数据类型
String resultType = element.attributeValue("resultType");
//反射创建对象
Class clazz = Class.forName(resultType);
obj = clazz.newInstance();
//获取ResultSet数据
ResultSetMetaData rsmd = rs.getMetaData();
//遍历实体类属性集合,依次将结果集中的值赋给属性
Field[] fields = clazz.getDeclaredFields();
for(int i = 0; i < fields.length; i++){
Object value = setFieldValueByResultSet(fields[i],rsmd,rs);
//通过属性名找到对应的setter方法
String name = fields[i].getName();
name = name.substring(0, 1).toUpperCase() + name.substring(1);
String MethodName = "set"+name;
Method methodObj = clazz.getMethod(MethodName,fields[i].getType());
//调用setter方法完成赋值
methodObj.invoke(obj, value);
}
}
代码的实现大致思路如上所述,具体实现起来有很多细节需要处理
上述操作使用的两个工具类完整代码如下
ParseXML
public class ParseXML {
//读取数据源配置信息
public static Map getProperties(){
Map map = new HashMap();
SAXReader reader = new SAXReader();
try {
Document document = reader.read("src/config.xml");
//获取根节点
Element root = document.getRootElement();
Iterator iter = root.elementIterator();
while(iter.hasNext()){
Element e = (Element) iter.next();
//解析environments节点
if("environments".equals(e.getName())){
Iterator iter2 = e.elementIterator();
while(iter2.hasNext()){
//解析environment节点
Element e2 = (Element) iter2.next();
Iterator iter3 = e2.elementIterator();
while(iter3.hasNext()){
Element e3 = (Element) iter3.next();
//解析dataSource节点
if("dataSource".equals(e3.getName())){
if("POOLED".equals(e3.attributeValue("type"))){
Iterator iter4 = e3.elementIterator();
//获取数据库连接信息
while(iter4.hasNext()){
Element e4 = (Element) iter4.next();
map.put(e4.attributeValue("name"),e4.attributeValue("value"));
}
}
}
}
}
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return map;
}
//根据接口查找对应的mapper.xml
public static String getMapperXML(String className){
//保存xml路径
String xml = "";
SAXReader reader = new SAXReader();
Document document;
try {
document = reader.read("src/config.xml");
Element root = document.getRootElement();
Iterator iter = root.elementIterator();
while(iter.hasNext()){
Element mappersElement = (Element) iter.next();
if("mappers".equals(mappersElement.getName())){
Iterator iter2 = mappersElement.elementIterator();
while(iter2.hasNext()){
Element mapperElement = (Element) iter2.next();
//com.cehnjie.dao.StudentDAO . 替换 #
className = className.replace(".", "#");
//获取接口结尾名
String classNameEnd = className.split("#")[className.split("#").length-1];
String resourceName = mapperElement.attributeValue("resource");
//获取resource结尾名
String resourceName2 = resourceName.split("/")[resourceName.split("/").length-1];
//StudentDAO.xml . 替换 #
resourceName2 = resourceName2.replace(".", "#");
String resourceNameEnd = resourceName2.split("#")[0];
if(classNameEnd.equals(resourceNameEnd)){
xml="src/"+resourceName;
}
}
}
}
} catch (DocumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return xml;
}
}
MyInvocationHandler
public class MyInvocationHandler implements InvocationHandler{
private String className;
public Object getInstance(Class cls){
//保存接口类型
className = cls.getName();
Object newProxyInstance = Proxy.newProxyInstance(
cls.getClassLoader(),
new Class[] { cls },
this);
return (Object)newProxyInstance;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SAXReader reader = new SAXReader();
//返回结果
Object obj = null;
try {
//获取对应的mapper.xml
String xml = ParseXML.getMapperXML(className);
Document document = reader.read(xml);
Element root = document.getRootElement();
Iterator iter = root.elementIterator();
while(iter.hasNext()){
Element element = (Element) iter.next();
String id = element.attributeValue("id");
if(method.getName().equals(id)){
//获取信息,创建数据源对象
Map map = ParseXML.getProperties();
ComboPooledDataSource datasource = new ComboPooledDataSource();
datasource.setDriverClass(map.get("driver"));
datasource.setJdbcUrl(map.get("url"));
datasource.setUser(map.get("username"));
datasource.setPassword(map.get("password"));
datasource.setInitialPoolSize(20);
datasource.setMaxPoolSize(40);
datasource.setMinPoolSize(2);
datasource.setAcquireIncrement(5);
Connection conn = datasource.getConnection();
//获取sql语句
String sql = element.getText();
//获取参数类型
String parameterType = element.attributeValue("parameterType");
//创建pstmt
PreparedStatement pstmt = createPstmt(sql,parameterType,conn,args);
ResultSet rs = pstmt.executeQuery();
if(rs.next()){
//读取返回数据类型
String resultType = element.attributeValue("resultType");
//反射创建对象
Class clazz = Class.forName(resultType);
obj = clazz.newInstance();
//获取ResultSet数据
ResultSetMetaData rsmd = rs.getMetaData();
//遍历实体类属性集合,依次将结果集中的值赋给属性
Field[] fields = clazz.getDeclaredFields();
for(int i = 0; i < fields.length; i++){
Object value = setFieldValueByResultSet(fields[i],rsmd,rs);
//通过属性名找到对应的setter方法
String name = fields[i].getName();
name = name.substring(0, 1).toUpperCase() + name.substring(1);
String MethodName = "set"+name;
Method methodObj = clazz.getMethod(MethodName,fields[i].getType());
//调用setter方法完成赋值
methodObj.invoke(obj, value);
}
}
conn.close();
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return obj;
}
/**
* 根据条件创建pstmt
* @param sql
* @param parameterType
* @param conn
* @param args
* @return
* @throws Exception
*/
public PreparedStatement createPstmt(String sql,String parameterType,Connection conn,Object[] args) throws Exception{
PreparedStatement pstmt = null;
try {
switch(parameterType){
case "int":
int start = sql.indexOf("#{");
int end = sql.indexOf("}");
//获取参数占位符 #{name}
String target = sql.substring(start, end+1);
//将参数占位符替换为?
sql = sql.replace(target, "?");
pstmt = conn.prepareStatement(sql);
int num = Integer.parseInt(args[0].toString());
pstmt.setInt(1, num);
break;
case "java.lang.String":
int start2 = sql.indexOf("#{");
int end2 = sql.indexOf("}");
String target2 = sql.substring(start2, end2+1);
sql = sql.replace(target2, "?");
pstmt = conn.prepareStatement(sql);
String str = args[0].toString();
pstmt.setString(1, str);
break;
default:
Class clazz = Class.forName(parameterType);
Object obj = args[0];
boolean flag = true;
//存储参数
List
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理 。多学习研究优秀框架的实现思路,对提升自己的编码能力大有裨益 。
页面更新:2024-05-22
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号