mybatis如何工作

news/2024/5/19 1:22:43 标签: mybatis, java, jdbc, 原理, 手写

如果自己能够写一个模仿mybatis工作的程序,那么看mybatis的源码就会很容易。

how mybatis works?

      • pom
      • 配置文件与java类的映射
      • 加载配置文件
      • 执行sql并为实体类填充值
      • sqlsession
      • Test

pom

java">  <dependencies>
	  <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.47</version>
	  </dependency>
	  <dependency>
		  <groupId>junit</groupId>
		  <artifactId>junit</artifactId>
		  <version>4.12</version>
		  <scope>test</scope>
	  </dependency>
  </dependencies>

我们没有依赖mybatis,但是我们要实现和mybatis类似的功能,就是直接调用mapper接口进行查询。

我们的包结构:

java_34">配置文件与java类的映射

java一切皆对象。

我们首先要把db.propertiesuserMapper.xml给读进来存储到对象中。

db.properties的对象叫做Configuration

java">public class Configuration {
	private String driver;
	private String url;
	private String username;
	private String password;

	private Map<String,MapperStatement> map = new HashMap<String, MapperStatement>();
	
	public String getDriver() {
		return driver;
	}

	public void setDriver(String driver) {
		this.driver = driver;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	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;
	}

	public Map<String, MapperStatement> getMap() {
		return map;
	}

	@Override
	public String toString() {
		return "Configuration{" +
				"driver='" + driver + '\'' +
				", url='" + url + '\'' +
				", username='" + username + '\'' +
				", password='" + password + '\'' +
				", map=" + map +
				'}';
	}
}

userMapper.xml的对象叫做MapperStatement

java">public class MapperStatement {
	private String namespace;
	private String id;
	private String resultType;
	private String sql;

	public String getNamespace() {
		return namespace;
	}

	public void setNamespace(String namespace) {
		this.namespace = namespace;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getResultType() {
		return resultType;
	}

	public void setResultType(String resultType) {
		this.resultType = resultType;
	}

	public String getSql() {
		return sql;
	}

	public void setSql(String sql) {
		this.sql = sql;
	}

	@Override
	public String toString() {
		return "MapperStatement{" +
				"namespace='" + namespace + '\'' +
				", id='" + id + '\'' +
				", resultType='" + resultType + '\'' +
				", sql='" + sql + '\'' +
				'}';
	}
}

注意在Configuration中,有一个属性new HashMap<String, MapperStatement>(),它的作用是通过key找到唯一的MapperStatement

那么需要哪些条件才能做到呢?

只需要namespaceid就行。知道这两个值,就能找到唯一确定的sql(找到MapperStatement也是为了找到sql)。

我们的Configuration最终将长这个样子:

java">Configuration
{driver='com.mysql.jdbc.Driver', 

url='jdbc:mysql://localhost:3306/mybatis?userUnicode=true&characterEncoding=utf-8', 

username='root', 

password='123456', 

map={
com.ocean.mapper.UserMapper.findAll=MapperStatement{
namespace='com.ocean.mapper.UserMapper', id='findAll', resultType='com.ocean.entity.User', 
sql='
		select * from t_user;
	'}, 

com.ocean.mapper.UserMapper.selectByPrimaryKey=MapperStatement{
namespace='com.ocean.mapper.UserMapper', id='selectByPrimaryKey', resultType='com.ocean.entity.User', 
sql='
	select id, username from t_user where id = ?
'}
	}
		}

加载配置文件

加载配置文件要在最开始做,也就是初始SqlSessionFactory的时候做:

java">/**
 * load db.properties and xxxmapper.xml
 */
public class SqlSessionFactory {
	private Configuration configuration;
	private static final String MAPPER_FOLDER = "./mapper";
	private static final String DB_LOCATION = "./db.properties";

	public SqlSessionFactory() {
		configuration = new Configuration();
		loadDBInfo();
		loadMappers();
		System.out.println("init sql session factory, the configuration is : " + configuration);
	}

	public SqlSession openSession(){
		return new DefaultSession(configuration);
	}

	private void loadMappers() {
		URL resource = SqlSessionFactory.class.getClassLoader().getResource(MAPPER_FOLDER);
		File mapper = new File(resource.getFile());
		if (mapper.isDirectory()) {
			File[] files = mapper.listFiles();
			for (File file : files) {
				loadMapper(file);
			}
		}

	}

	private void loadMapper(File file) {
		SAXReader reader = new SAXReader();
		try {
			Document document = reader.read(file);
			Element rootElement = document.getRootElement();
			String namespace = rootElement.attributeValue("namespace");
			List<Element> elements = rootElement.elements();
			for (Element element : elements) {
				MapperStatement mapperStatement = new MapperStatement();
				String id = element.attributeValue("id");
				String resultType = element.attributeValue("resultType");
				String sql = element.getData().toString();
				mapperStatement.setId(id);
				mapperStatement.setNamespace(namespace);
				mapperStatement.setResultType(resultType);
				mapperStatement.setSql(sql);

				configuration.getMap().put(namespace + "." + id, mapperStatement);
			}
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void loadDBInfo() {
		InputStream inputStream = SqlSessionFactory.class.getClassLoader().getResourceAsStream(DB_LOCATION);
		Properties properties = new Properties();

		try {
			properties.load(inputStream);
			configuration.setDriver(properties.getProperty("driver"));
			configuration.setUrl(properties.getProperty("url"));
			configuration.setUsername(properties.getProperty("username"));
			configuration.setPassword(properties.getProperty("password"));
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}


}

这里就是用Properties读取数据库连接数据:

java">driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?userUnicode=true&characterEncoding=utf-8
username=root
password=123456

SAXReader读xml:

java"><?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.ocean.mapper.UserMapper">
	<!--User selectByPrimaryKey(Integer id);-->
<select id="selectByPrimaryKey" resultType="com.ocean.entity.User">
	select id, username from t_user where id = ?
</select>

<!--List<User> findAll();-->
	<select id="findAll" resultType="com.ocean.entity.User">
		select * from t_user;
	</select>

</mapper>

为了测试,我们只准备了两个查询。

这时候我正好可以介绍实体类和mapper接口:

User

java">public class User implements Serializable {
	private Integer id;
	private String username;

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	@Override
	public String toString() {
		return "User{" +
				"id=" + id +
				", username='" + username + '\'' +
				'}';
	}
}

mapper

java">public interface UserMapper {

	User selectByPrimaryKey(Integer id);

	List<User> findAll();
}

nothing special。

执行sql并为实体类填充值

在不知道下一步怎么办的情况下,我们知道,用jdbc查sql这一步是逃不了的。

所以,先写jdbc

java">public interface Executor {
	<T>List<T> query(MapperStatement mapperStatement, Object parameter);
}

我们传进MapperStatement和参数。并统一返回list。

java">/**
 * given the configuration loaded by SqlSessionFactory, execute the standard query using jdbc
 */
public class DefaultExecutor implements Executor {

	private Configuration configuration;

	public DefaultExecutor(Configuration configuration) {
		this.configuration = configuration;
	}

	/**
	 * a standard query using jdbc
	 * @param mapperStatement
	 * @param parameter
	 * @param <T>
	 * @return
	 */
	@Override
	public <T> List<T> query(MapperStatement mapperStatement, Object parameter) {
		List<T> list = new ArrayList<T>();
		Connection connection = null;
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;

		try {
			Class.forName(configuration.getDriver());

			connection = DriverManager
					.getConnection(configuration.getUrl(), configuration.getUsername(), configuration.getPassword());
			preparedStatement = connection.prepareStatement(mapperStatement.getSql());
			System.out.println("prepared statement is : " + preparedStatement);
			parameterize(preparedStatement, parameter);
			resultSet = preparedStatement.executeQuery();
			handleResultset(list, resultSet, mapperStatement);

			System.out.println("list containing entity is : " + list);

		}
		catch (Exception e) {
			e.printStackTrace();
		}
		finally {
			if (null != connection) {
				try {
					connection.close();
				}
				catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if (null != preparedStatement) {
				try {
					preparedStatement.close();
				}
				catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if (null != resultSet) {
				try {
					resultSet.close();
				}
				catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return list;
	}

	/**
	 * set the result set value to the entity field
	 * @param list
	 * @param resultSet
	 * @param mapperStatement
	 * @param <T>
	 * @throws Exception
	 */
	private <T> void handleResultset(List<T> list, ResultSet resultSet, MapperStatement mapperStatement) throws Exception {
		Class<?> clazz = Class.forName(mapperStatement.getResultType());
		while (resultSet.next()) {
			Object entity = clazz.newInstance();
			ReflectionUtil.setPropertyFromResultSet(entity, resultSet);
			list.add((T) entity);
		}
	}

	/**
	 * set value to prepared statement with parameter
	 * @param preparedStatement
	 * @param parameter
	 * @throws Exception
	 */
	private void parameterize(PreparedStatement preparedStatement, Object parameter) throws Exception {
		if (parameter instanceof String) {
			preparedStatement.setString(1, (String) parameter);
		}
		else if (parameter instanceof Integer) {
			preparedStatement.setInt(1, (Integer) parameter);
		}
	}
}

由于我们不知道传进来的sql是怎么样的,所以我们要用parameterize方法动态地处理参数。

查出来的结果在ResultSet中,我们要把它的值赋给实体类。

我们使用了一个工具类:ReflectionUtil.setPropertyFromResultSet(entity, resultSet);

java">public class ReflectionUtil {

	/**
	 * take the value of result set out, and then give it to the entity field
	 * @param entity
	 * @param resultSet
	 * @throws Exception
	 */
	public static void setPropertyFromResultSet(Object entity, ResultSet resultSet)throws Exception{
		Field[] declaredFields = entity.getClass().getDeclaredFields();

		for (int i = 0; i < declaredFields.length; i++) {
			if (declaredFields[i].getType().getSimpleName().equals("String")) {
				setPropertyToEntity(entity,declaredFields[i].getName(),resultSet.getString(declaredFields[i].getName()));
			}else if(declaredFields[i].getType().getSimpleName().equals("Integer")){
				setPropertyToEntity(entity,declaredFields[i].getName(),resultSet.getInt(declaredFields[i].getName()));
			}
		}
	}
	
	/**
	 * set value of field for entity
	 * @param entity
	 * @param name
	 * @param value
	 * @throws Exception
	 */
	private static void setPropertyToEntity(Object entity, String name, Object value) throws Exception{
		Field field;
		field = entity.getClass().getDeclaredField(name);
		field.setAccessible(true);
		field.set(entity,value);
	}
}

它的目的就是把resultset中的值一一取出来,给User赋上。

sqlsession

该做的工作都做好了。

暴露给程序员的就是一个session,它代表着与数据库的一次连接。

我们看一下之前mybatis是怎么做的:

java">  public static SqlSessionFactory getSqlSessionFactory() throws Exception {
        String resources = "mybatis-config.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resources);
        return new SqlSessionFactoryBuilder().build(resourceAsStream);
    }

    @Test
    public void testCreateEmp() throws Exception {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            Employee employee = new Employee();
            employee.setLastName("Yeats");
            employee.setEmail("78657@test.com");
            Integer result = employeeMapper.createEmployee(employee);
            System.out.println("if created successfully? " + result);

            sqlSession.commit();
        } finally {
            sqlSession.close();
        }

    }

sqlSession有一个getMapper的方法,并且返回一个代理对象。

并且带代理对象去执行具体的增删改查方法。

如果是用jdk动态代理来做的,当上述代码执行createEmployee方法时,一定会被一个InvocationHandler拦截到,并且执行invoke方法。

我的思路是把真正要执行的查询放在invoke方法中,这个增删改查工作是由SqlSession完成的,当然,SqlSession肯定封装了底层干活的Executor

上面是大体的思路。

首先我们需要一个SqlSession

java">public interface SqlSession {
	<T> T selectById(String sourceId, Object parameter);
	<T>List<T> selectAll(String sourceId, Object parameter);
	<T> T getMapper(Class<T> type);
}

它一定会有getMapper方法,一定会有session层面的增删改查方法。

然后是实现类:

java">/**
 * default session implementation
 *
 * @author
 */
public class DefaultSession implements SqlSession {

	private Configuration configuration;
	private Executor executor;

	public DefaultSession(Configuration configuration){
		this.configuration=configuration;
		executor=new DefaultExecutor(configuration);
	}

	@Override
	public <T> T selectById(String sourceId, Object parameter) {
		List<Object> objects = selectAll(sourceId, parameter);
		if(objects==null||objects.size()<0){
			return null;
		}else if(objects.size()>1){
			throw new RuntimeException("too many results");
		}else {
			Object ret = objects.get(0);
			return (T) ret;
		}
	}

	@Override
	public <T> List<T> selectAll(String sourceId, Object parameter) {
		MapperStatement mapperStatement = configuration.getMap().get(sourceId);
		return executor.query(mapperStatement,parameter);
	}

	@Override
	public <T> T getMapper(Class<T> type) {
		MapperHandler mapperHandler = new MapperHandler(this);
		Object proxyInstance = Proxy.newProxyInstance(type.getClassLoader(), new Class[] {type}, mapperHandler);
		return (T)proxyInstance;
	}
}

先看selectByIdselectAll这两个方法,它们最终是让Executor去干活的,这和我们的设计思路是一致的。

然后是getMapper。我们用Proxy.newProxyInstance的方式返回一个代理。

所需要的参数是一个系统类加载器,一个接口对象,一个InvocationHandler

我们把InvocationHandler写出来:

java">/**
 * the handler of a proxy instance of a mapper
 */
public class MapperHandler implements InvocationHandler {
	private SqlSession sqlSession;

	public MapperHandler(SqlSession sqlSession) {
		this.sqlSession = sqlSession;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		if ("java.lang.Object".equals(method.getDeclaringClass().getName())) {
			return null;
		}

		System.out.println("sourceId : " + method.getDeclaringClass().getName() + "." + method.getName());

		//if return type is instance of Collection(in our case , it's a list), execute selectAll
		if (Collection.class.isAssignableFrom(method.getReturnType())) {
			return sqlSession.selectAll(method.getDeclaringClass().getName() + "." + method.getName(), args==null?null:args[0]);
		}
		else {
			return sqlSession.selectById(method.getDeclaringClass().getName() + "." + method.getName(), args==null?null:args[0]);
		}
	}
}

invoke方法里,我们委托sqlSession干活。

java">if ("java.lang.Object".equals(method.getDeclaringClass().getName())) 
{
	return null;
}

这句很奇怪,因为程序有bug,我只有这么加才能有结果,知道bug原因的朋友可以告诉我。

Test

java">public class MyBatisTest {

	public static void main(String[] args) throws Exception {
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactory();
		SqlSession session = sqlSessionFactory.openSession();

		UserMapper userMapper = session.getMapper(UserMapper.class);
		List<User> all = userMapper.findAll();
		all.forEach(System.out::println);

		/*User userOne = userMapper.selectByPrimaryKey(1);
		System.out.println("userOne : "+userOne);*/
	}
}

得到正确结果。完事。

但是Proxy.newProxyInstance(type.getClassLoader(), new Class[] {type}, mapperHandler);这段是有问题的。

我debug了一下,proxy0生成了,构造器也顺利拿到了,就是

java"> return cons.newInstance(new Object[]{h});

返回了一个null。

知道为什么的朋友可以告诉我。


http://www.niftyadmin.cn/n/1312173.html

相关文章

pands 画图 调整大小_关于数学建模的画图学习建议

数学建模&#xff0c;PPT&#xff0c;visio, matlab就够用了&#xff0c;其他根据特点备选。学会PPT里面的形状操作&#xff0c;搭建常用的示意图&#xff0c;还有调色等。matlab二维画图&#xff0c;三维画图会几个命令就行。其中二维画图用主要用plot&#xff08;包括好看的图…

oracle开发错误

先展示一个错误写法 public static String printGg_bysly0List() {// 外网TransManager tm new TransManager();try {// 获取要更改的人员String sql "select t.yrk057,t.aac002,t.yr0560 from gg_bysly0 t where t.yr0560 like 2018%";List<Gg_bysly0> syb…

Java如何加入皮肤_java皮肤包怎么才能使用

你的位置:问答吧-> JAVA-> 问题详情java皮肤包怎么才能使用在这里下了一个&#xff1a;http://download.csdn.net/source/760960不是jar文件&#xff0c;跟平常写的程序结构一样导入一个工程之后&#xff0c;出现很多包。。作者: junkli发布时间: 2010-08-06我晕死....什…

lc滤波电路电感电容值选择_π型滤波在开关电源的作用

常见的π型滤波是两个电容中间一个电感&#xff0c;有RC和LC两种类型&#xff0c;一般在输出电流不大的情况下用RC&#xff0c;并且R的取值不能太大&#xff0c;效果一般不如LC电路。在LC电路里有一个电感&#xff0c;可以根据输出电流大小和频率高低选择电感量的大小&#xff…

世纪前线网络质量测试工具 是什么_上海控安发布汽车信息安全评估工具箱:一款标准化、自动化的安全测试工具...

汽车网联化和智能化导致车载网络更为开放和复杂&#xff0c;面临着严峻的信息安全风险和挑战&#xff0c;汽车安全测试工作备受重视。安全测试行业现状及痛点&#xff1a;• 工程师主要通过人为分析进行测试建模&#xff0c;对整车或零部件进行信息安全测试&#xff0c;极大依赖…

手机电脑的芯片主要是由_威图手机-世界上最贵的奢侈品手机之一

威图手机【威图手机】导读:中国的智能手机产品&#xff0c;以其性价比优势为人熟知&#xff0c;现在&#xff0c;中国智能手机产品这个大家庭又迎来一位新成员。有一点不同的是&#xff0c;这位新成员的客户主要是那些穿着西装革履的高端商务人士&#xff0c;配备该品牌由蓝宝石…

windows功能_怎么样在windows上实现文件预览功能?一个软件搞定,提高效率

相信用过mac系统的朋友应该都知道&#xff0c;在mac系统中文本、图片或者视频等文件后&#xff0c;直接按空格就能进行文件内容的预览。在找文本、图片、视频或者音频的时候&#xff0c;这个功能是非常的方便实用的。不需要打开文件就能看到文件的内容了&#xff0c;可以说用了…

表与表之间的连接

其实&#xff0c;表A与表B的连接和高中时候学的集合是一样的。 要让两个表能够连接&#xff0c;必须有字段是一样的&#xff0c;或者说&#xff0c;是通过这个字段联系这两张表的。 比如表A是员工表&#xff0c;员工表里有一个字段为dept_id。表B是部门表&#xff0c;部门表的…