mybatis的简单模拟

标签: mybatis  java

我们首先来观察 mybatis-config.xml,里面需要配置一个数据源,那么我们准备一个数据源的实体类。

mybatis-config.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="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <!-- 配置数据源 -->
      <dataSource type="POOLED">
        <property name="driver" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl"/>
        <property name="username" value="scott"/>
        <property name="password" value="123456"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="com/me/mybatis/DeptMapper.xml"/>
  </mappers>
</configuration>

数据源的实体类:

对于mapper的映射文件,我们也可以提取成一个实体类,我们观察mapper.xml发现,我们可以从中把 sql语句、参数类型、返回值类型、是否为更新的sql语句  组成一个实体类。

DeptMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>

<mapper namespace="com.me.bean.DeptMapper">
 <insert id="addDept" parameterType="com.me.bean.DeptBean">
   insert into dept values(#{deptno},#{dname},#{loc}) 
 </insert>
 
  <insert id="addDept1" parameterType="map">
   insert into dept values(#{deptno},#{dname},#{loc}) 
 </insert>
 
  <select id="findAll" resultType="com.me.bean.DeptBean">
    select deptno,dname,loc from dept 
  </select>
</mapper>

mapper实体类: 

 我们查询oracle中的dept表作为测试,创建一个dept的实体类

 接下来具体实现。

先看看解析mybatis-config.xml的MyBatisConfig.java。使用dom4j解析xml。这个类主要解析了 数据源信息、mapper映射路径信息。

public class MyBatisConfig {
	private DataSource dataSource;  //数据源实体
	private List<String> mappers=new ArrayList<String>(); //保存mapper映射文件
	public MyBatisConfig(String config) {
		try {
			readXml(config);
		} catch (DocumentException e) {
			e.printStackTrace();
		}
	}
	/**
	 * 解析mybatis-config.xml
	 * @param config //文件路径
	 * @throws DocumentException
	 */
	private void readXml(String config) throws DocumentException {
		SAXReader reader = new SAXReader();
		Document doc = reader.read(this.getClass().getClassLoader().getResourceAsStream(config));
		
		XPath xPath = doc.createXPath("//dataSource/property"); //解析mybatis-config.xml下的property所有节点
		List<Element> properties = xPath.selectNodes(doc);
		
		dataSource=new DataSource();//创建一个数据源实体
		String pname=null;
		for (Element e1:properties) { //获取所有的property节点信息,保存到数据源实体类中
			pname=e1.attributeValue("name");
			switch (pname) {
			case "driver":
				dataSource.setDriver(e1.attributeValue("value"));
				break;
			case "url":
				dataSource.setUrl(e1.attributeValue("value"));
				break;
			case "username":
				dataSource.setUsername(e1.attributeValue("value"));
				break;
			case "password":
				dataSource.setPassword(e1.attributeValue("value"));
				break;
			
			default:
				break;
			}
		}
		
		//System.out.println(dataSource);
		//开始获取mybatis-config.xml中的<mappers>节点,即映射文件
		xPath = doc.createXPath("//mappers/mapper");
		List<Element> list = xPath.selectNodes(doc);
		for (Element el:list) {
			mappers.add(el.attributeValue("resource")); //保存到一个集合中
		}
		//到这里我们已经解析完了mybatis-config.xml
		//System.out.println(mappers); 
	}
	
	public DataSource getDataSource() {
		return dataSource;
	}
	
	public List<String> getMappers() {
		return mappers;
	}
	
	public static void main(String[] args) {
		MyBatisConfig myBatisConfig = new MyBatisConfig("mybatis-config.xml");
	}
}

模拟SqlSessionFactory的SqlSessionFactory.java。这个类解析了mapper映射文件,并将解析出来的信息保存到实体类MapperInfo中。

public class SqlSessionFactory {

	private MyBatisConfig config;  //mybatis-config.xml的解析结果
	private Map<String, MapperInfo> mapperInfos =new HashMap<String, MapperInfo>();//mapper映射文件的解析结果

	public  SqlSessionFactory(String configXml) {
		config=new MyBatisConfig(configXml);  //解析mybatis-config.xml
		try {
			parseXml(); //解析mapper映射文件
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 解析mapper映射文件
	 * @throws DocumentException
	 */
	private void parseXml() throws DocumentException {
		List<String> mappers = config.getMappers(); //获取映射文件路径
		if (mappers!=null && !mappers.isEmpty()) {
			SAXReader reader = new SAXReader();
			Document doc= null;
			XPath xPath = null;
			List<Element> ops=null;

			String opname=null;
			MapperInfo mapperInfo=null;

			//循环解析所有映射文件
			for (String mapper:mappers) {
				doc = reader.read(this.getClass().getClassLoader().getResourceAsStream(mapper));
				xPath=doc.createXPath("//mapper/*"); //获取DeptMapper.xml下mapper下的所有节点
				ops=xPath.selectNodes(doc);

				for (Element el:ops) {
					mapperInfo=new MapperInfo(); //创建一个mapper实体类
					opname=el.getName();
					//如果是select节点,说明不是更新语句
					if (opname.equals("select")) { 
						mapperInfo.setUpdate(false);
					}
					//将各项信息填入新创建的mapper实体类
					mapperInfo.setParameterType(el.attributeValue("parameterType"));
					mapperInfo.setResultType(el.attributeValue("resultType"));
					mapperInfo.setSql(el.getTextTrim());

					//保存结果到一个集合中
					mapperInfos.put(el.attributeValue("id"), mapperInfo);
				}
			}
		}
	}

	public MyBatisConfig getConfig() {
		return config;
	}

	public Map<String, MapperInfo> getMapperInfos() {
		return mapperInfos;
	}
}

最后就是比较难实现的SqlSession。这个类反射机制用的比较多,代码有详细注释。

public class SqlSession {

	private SqlSessionFactory factory; //SqlSessionFactory
	private DBHelper db;

	public SqlSession(SqlSessionFactory factory){
		this.factory=factory;
		db=new DBHelper(factory.getConfig().getDataSource()); //用已经解析好的数据源实体类去初始化DBhelper
	}

	/**
	 * 查询
	 * @param sqlId mapper映射文件下的节点id
	 * @param params 参数
	 * @return
	 */
	public <T> List<T> selectList(String sqlId,Object ... params) {
		String sql;//要执行的sql语句
		Class C;  //反射的对象
		try {
			MapperInfo mapperInfo=factory.getMapperInfos().get(sqlId);//获取该sqlId节点下的所有信息
			if (mapperInfo==null) {
				return null;
			}

			sql=mapperInfo.getSql(); //获取sql语句
			if (mapperInfo.isUpdate()) {
				return null;
			}

			String className = mapperInfo.getResultType();//获取返回值类型
			C=Class.forName(className); //反射得到该返回值类型的class
			return db.findObject(sql, C,params); //调用dbhelper按对象查询

		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	/**
	 * 更新情况(update,insert,delete)
	 * @param sqlId
	 * @param obj
	 * @return
	 */
	public int update(String sqlId,Object obj) {
		int result=0; //执行结果

		MapperInfo mapperInfo = factory.getMapperInfos().get(sqlId);
		if (mapperInfo==null) {
			return 0;
		}

		String sql=mapperInfo.getSql();
		String className = mapperInfo.getParameterType();

		Pattern pattern = Pattern.compile("[#][{]\\w+}"); //正则表达式 。将#{xxxx}变成#{?}
		Matcher matcher = pattern.matcher(sql);

		ArrayList<String> colNames = new ArrayList<String>();
		while (matcher.find()) {
			colNames.add(matcher.group().replaceAll("[#{}]*", ""));
		}
		sql=matcher.replaceAll("?");
		//如果参数类型为map
		if ("map".equals(className)) {
			Map<String, Object> map = (Map<String, Object>) obj;
			List<Object> params=new ArrayList<Object>(); //参数集合
			for (String colName:colNames) {
				params.add(map.get(colName));
			}
			return db.update(sql, params);//使用DBhelper查询
		}
		if ("String".equals(className)) {
			return -1;
		}

		//参数类型为对象时
		try {
			Class c =Class.forName(className); //反射得到class
			Method[] methods=c.getDeclaredMethods();  //获取该类中的声明方法
			List<Method> getMethods = new ArrayList<Method>();  //保存该对象的get方法
			for (Method md:methods) {
				if (md.getName().startsWith("get")) { 
					getMethods.add(md); //如果方法为get方法,保存
				}
			}
			List<Object> params=new ArrayList<Object>();//参数集合
			String mname;
			for (String colName:colNames) { 
				for(Method md:getMethods){
					mname="get"+colName.substring(0,1).toUpperCase()+colName.substring(1);//转换为getXxx的形式,例如getdeptno转换为getDeptno
					if (mname.equals(md.getName())) { //用get方法或的参数
						params.add(md.invoke(obj));  //反向执行
					}
				}
			}
			return db.update(sql, params); //返回执行结果
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}
}

测试类:

public class _Main {

	@Test
	public void test() {
		SqlSessionFactory sqFactory = new SqlSessionFactory("mybatis-config.xml");
		//System.out.println(sqFactory.getMapperInfos());
		SqlSession session = new SqlSession(sqFactory);
		List<Object> dept = session.selectList("findAll");
		System.out.println(dept);
	}
	@Test
	public void test2() {
		SqlSessionFactory sqFactory = new SqlSessionFactory("mybatis-config.xml");
		SqlSession session = new SqlSession(sqFactory);
		int result=session.update("addDept", new DeptBean(88,"hah","666"));
		System.out.println(result);
		
	}
	
	@Test
	public void test3() {
		SqlSessionFactory sqFactory = new SqlSessionFactory("mybatis-config.xml");
		SqlSession session = new SqlSession(sqFactory);
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("deptno", 99);
		map.put("dname", "呵呵");
		map.put("loc", "888");
		int result=session.update("addDept1", map);
		System.out.println(result);
	}
}

 DBHelper部分代码;

public class DBHelper {
	private DataSource dataSource;
	public  DBHelper(DataSource dataSource) {
		this.dataSource=dataSource;
		try {
			Class.forName(dataSource.getDriver());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 获取连接
	 * @return
	 */
	public Connection getConnection() {
		Connection con = null;
		try {
			con = DriverManager.getConnection(dataSource.getUrl(),dataSource.getUsername(),dataSource.getPassword());
		    //Context context = new InitialContext();
		    //DataSource ds = (DataSource) context.lookup("java:comp/env/mysql");
		  //  con = ds.getConnection();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return con;
	}
}