git地址:点我
在手写mybatis简化版框架先了解一下mybatis框架的执行流程。
一、Mybatis框架执行流程
1.配置文件有两种,一个为主配置文件,一个为映射文件。
主配置文件:配置了jdbc等环境信息。
映射文件:配置了接口对应的sql语句映射。
这两个配置文件会被封装到Configuration中。
2.通过mybatis配置文件得到SqlSessionFactory。
3.通过SqlSessionFactory得到SqlSession,一个sqlSession相当于一个request请求。
4.SqlSession调用Executor执行器来操作数据库。
5.解析入参,封装成statement,执行,映射结果返回。
将mybatis主要流程分析了一下,思路明确了,那么设计一下简化版的mybatis框架来完成一个查询。
二、简化版ybatis
同上面过程,毕竟是简化版,就不使用sqlsession工厂了,跳过这一步。
即:1.读取xml->2.调用sqlsession->3.调用executor->4.解析参数执行并返回映射结果
先上结果图:
项目结构图:maven项目
1.连接数据库
<?xml version="1.0" encoding="UTF-8"?>
<dataSource>
<property name="driverClassName">com.mysql.jdbc.Driver</property>
<property name="url">jdbc:mysql://localhost:3306/test</property>
<property name="username">root</property>
<property name="password">root</property>
</dataSource>
内容为jdbc连接信息,哦忘了,先把pom文件展示一下,两个jar包:mysql连接和dom4j解析
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>
接下来是通过代码将该xml文件解析出来。
//启动应用程序类加载器
private static ClassLoader classLoader = ClassLoader.getSystemClassLoader();
/*
解析xml文件
*/
public Element parseXML(String resource) {
try {
//返回用于读取指定资源的输入流。
InputStream resourceAsStream = classLoader.getResourceAsStream(resource);
//使用dom4j方式解析
SAXReader saxReader = new SAXReader();
//使用SAX从给定流中读取文件
Document document = saxReader.read(resourceAsStream);
//获得文件的根节点
Element rootElement = document.getRootElement();
return rootElement;
} catch (DocumentException e) {
throw new RuntimeException("解析文件失败:"+resource);
}
}
/*
解析主配置xml文件节点
*/
public Map<String, String> parseNode(Element element){
//判断根目录名称
if (!element.getName().equals("dataSource")) {
throw new RuntimeException("主配置文件根名称必须是dataSource");
}
Map<String, String> map = new HashMap<String, String>();
map.put("driverClassName", null);
map.put("url", null);
map.put("username", null);
map.put("password", null);
//读取property属性内容
for (Object obj :element.elements("property")){
Element ele = (Element) obj;
String name = ele.attributeValue("name");
String value =ele.getText();
if (name==null||value==""){
throw new RuntimeException("格式错误,正确格式为 <property name=\"xxx\">xxx</property>");
}
if (name.equals("driverClassName")){
map.put("driverClassName",value);
}else if(name.equals("url")){
map.put("url",value);
}else if (name.equals("username")){
map.put("username",value);
}else if(name.equals("password")){
map.put("password",value);
}else {
throw new RuntimeException("不能识别的名称:"+name);
}
}
return map;
}
public Connection build(String resource){
Element element = parseXML(resource);
Map<String,String> jdbcMap =parseNode(element);
try {
Class.forName(jdbcMap.get("driverClassName"));
} catch (ClassNotFoundException e) {
throw new RuntimeException("驱动类未找到!");
}
Connection connection=null;
try {
connection = DriverManager.getConnection(jdbcMap.get("url"),jdbcMap.get("username"),jdbcMap.get("password"));
} catch (SQLException e) {
throw new RuntimeException("jdbc连接错误!");
}
return connection;
}
private Connection getConnection(){
Connection connection=myParse.build("mybatis-config.xml");
return connection;
}
从代码上看,该简化的ybatis主配置文件必须名为mybatis-config.xml
可以自己写一个测试类,看是否报错,没报错则进行下一步。
2.sqlSession代理
sqlSession肯定不会自己去执行,因为不能写死所以使用动态代理来使代理类去实现具体方法。
public class MySqlSession {
private MyExcutor excutor = new MyExcutorImpl(); //待会实现
private MyParse parse=new MyParse();
public <T> T selectObject(Mapping mapping, String param){
return excutor.query(mapping,param);
}
public <T> T getMapper(Class<T> cls){ //待会实现
return (T) Proxy.newProxyInstance(cls.getClassLoader(),new Class[]{cls},new MySqlSessionProxy(parse,this));
}
}
然后先写一点代理类,把mapper映射文件解析了。
/*
sqlSession代理类
*/
public class MySqlSessionProxy implements InvocationHandler {
private MyParse parse;
private MySqlSession sqlSession ;
private String PATH="mapper/";
public MySqlSessionProxy(MyParse parse,MySqlSession sqlSession){
this.parse=parse;
this.sqlSession=sqlSession;
}
public Object invoke(Object proxy, Method method, Object[] args) {
String name =method.getDeclaringClass().getName();
String mapperName=name.substring(name.lastIndexOf(".")+1);
MappingBean mappingBean=parse.parseMapper(parse.parseXML(PATH+mapperName+".xml"));
return null;
}
}
从这里可以看到该简化的ybatis的映射文件必须和接口名保持一致,并且在mapper文件夹里。
mapper.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="com.chuan.mapper.UserMapper">
<select id="getUserById" resultType="com.chuan.entity.User">
SELECT * FROM user WHERE id = ?
</select>
</mapper>
parseMapper()方法:
/*
解析mapper映射xml文件
*/
public MappingBean parseMapper(Element element){
//判断根目录名称
if (!element.getName().equals("mapper")) {
throw new RuntimeException("mapper映射文件根名称必须是mapper");
}
MappingBean mappingBean=new MappingBean();
String namespace=element.attributeValue("namespace");
if (namespace==null){
throw new RuntimeException("mapper映射文件namespace不存在");
}
mappingBean.setInterfaceName(namespace);
List<Mapping> mappingList = new ArrayList<Mapping>();
Iterator it=element.elementIterator();
while (it.hasNext()){
Element ele=(Element) it.next();
Mapping mapping =new Mapping();
String funcName =ele.attributeValue("id");
if (funcName==null){
throw new RuntimeException("mapper映射文件中id不存在");
}
String sqlType = ele.getName();
String paramType = ele.attributeValue("parameterType");
String resultType=ele.attributeValue("resultType");
String sql=ele.getText().trim();
mapping.setFuncName(funcName);
mapping.setSqlType(sqlType);
mapping.setParamType(paramType);
mapping.setSql(sql);
Object object=null;
try {
object=Class.forName(resultType).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
mapping.setResultType(object);
mappingList.add(mapping);
}
mappingBean.setMappingList(mappingList);
return mappingBean;
}
这里就完成了对mapper映射文件的解析,接下来到下一步,进入executor执行。
3.executor执行
在写executor之前先创建两个实体类,一个是对应接口实体类,一个是xml文件sql实体类。
sql实体类:
public class Mapping {
/*
sql语句
*/
private String sql;
/*
sql语句参数类型
*/
private String sqlType;
/*
入参类型
*/
private String paramType;
/*
返回参数类型
*/
private Object resultType;
/*
方法名
*/
private String funcName;
//getter、setter省略
}
接口实体类:
public class MappingBean {
/*
接口名
*/
private String interfaceName;
/*
接口名下所有方法
*/
private List<Mapping> mappingList;
//getter、setter省略
}
补齐代理类:
public Object invoke(Object proxy, Method method, Object[] args) {
String name =method.getDeclaringClass().getName();
String mapperName=name.substring(name.lastIndexOf(".")+1);
MappingBean mappingBean=parse.parseMapper(parse.parseXML(PATH+mapperName+".xml"));
if (mappingBean!=null&&(mappingBean.getMappingList()!=null&&mappingBean.getMappingList().size()>0)){
for (Mapping mapping :mappingBean.getMappingList()){
//进入查询逻辑
if (mapping.getSqlType().equals("select")){
if (mapping.getFuncName().equals(method.getName())){
System.out.println("执行查询方法:"+mapping.getSql());
System.out.println("参数:"+args[0]);
return sqlSession.selectObject(mapping,String.valueOf(args[0]));
}
}
}
}
return null;
}
通过sqlSession.selectObject调用到了executor方法,通过这里看到该简化的ybatis传参只接收一个,并且是字符串。
executor方法:这里通过反射将结果转换成对象,但只做了整型和字符串的转换。
public class MyExcutorImpl implements MyExcutor {
private MyParse myParse = new MyParse();
public <T> T query(Mapping mapping, Object param) {
Connection connection=getConnection();
PreparedStatement preparedStatement=null;
ResultSet resultSet=null;
Object obj=null;
List list=new ArrayList();
try {
preparedStatement=connection.prepareStatement(mapping.getSql());
preparedStatement.setString(1,param.toString());
if (mapping.getResultType()==null){
throw new RuntimeException("返回的映射结果不能为空!");
}
resultSet = preparedStatement.executeQuery();
int row = 0;
ResultSetMetaData rd = resultSet.getMetaData();
while (resultSet.next()){
obj=resultToObject(resultSet,mapping.getResultType());
row++;
list.add(obj);
}
System.out.println("记录行数:"+row);
} catch (SQLException e) {
e.printStackTrace();
}
return (T)list;
}
private Connection getConnection(){
Connection connection=myParse.build("mybatis-config.xml");
return connection;
}
/*
* 把得到的一列数据存入到一个对象中
*/
@SuppressWarnings("unchecked")
private <T> T resultToObject(ResultSet rs,Object object) {
Object obj=null;
try {
Class cls=object.getClass();
/*
这里为什么要通过class再new一个对象,因为如果不new一个新的对象,每次返回的都是形参上的object,
而这个object都是同一个,会导致list列表后面覆盖前面值。
*/
obj=cls.newInstance();
//获取结果集元数据(获取此 ResultSet 对象的列的编号、类型和属性。)
ResultSetMetaData rd=rs.getMetaData();
for (int i = 0; i < rd.getColumnCount(); i++) {
//获取列名
String columnName=rd.getColumnLabel(i+1);
//组合方法名
String methodName="set"+columnName.substring(0, 1).toUpperCase()+columnName.substring(1);
//获取列类型
int columnType=rd.getColumnType(i+1);
Method method=null;
switch(columnType) {
case java.sql.Types.VARCHAR:
case java.sql.Types.CHAR:
method=cls.getMethod(methodName, String.class);
if(method!=null) {
method.invoke(obj, rs.getString(columnName));
}
break;
case java.sql.Types.INTEGER:
method=cls.getMethod(methodName, Integer.class);
if(method!=null) {
method.invoke(obj, rs.getInt(columnName));
}
break;
default:
break;
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return (T) obj;
}
}
4.测试
将该简化版的ybatis打成jar包。
将导出的jar包引入到本地仓库。
命令:根据自己实际路径和名称修改。
mvn install:install-file -DgroupId=com.my -DartifactId=ybatis -Dversion=1.0.0 -Dpackaging=jar -Dfile=C:\Users\chuan\Desktop\glzxCode\ybatis\target\ybatis-1.0-SNAPSHOT.jar
新建一个maven项目。
添加pom文件:引入jar包,因为打包并没有将引入的jar打进去,所以要重新引用。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>com.my</groupId>
<artifactId>ybatis</artifactId>
<version>1.0.0</version>
</dependency>
实体类:
public class User implements Serializable {
private Integer id;
private String username;
private String password;
//getter、setter省略
}
接口:
public interface UserMapper {
public List<User> getUserById(String id);
}
映射文件:
<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="com.chuan.mapper.UserMapper">
<select id="getUserById" resultType="com.chuan.entity.User">
SELECT * FROM user WHERE id = ?
</select>
</mapper>
测试类:
public class TestMybatis {
public static void main(String[] args) {
MySqlSession sqlsession=new MySqlSession();
UserMapper mapper = sqlsession.getMapper(UserMapper.class);
List<User> user = mapper.getUserById("1");
System.out.println(user);
}
}
结果:
执行查询方法:SELECT * FROM user WHERE id = ?
参数:1
记录行数:2
[User{id='1', username='aaa', password='bbb'}, User{id='1', username='c', password='c'}]
到这里整个过程就已经结束了,目前只有查询功能,可能后期会增加其他功能以及缓存等。有问题欢迎指正,谢谢。