MyBatis系列教程(二)--模擬實現基於XML和Annotation的小型Mybatis(Simulation)

所需要用到的其他工具或技術:

項目管理工具 : Maven

測試運行工具 : Junit

數據庫 : Derby

XML操作工具:Dom4j


繼續不廢話

Maven Dependencies:

<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.9</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.derby</groupId>
			<artifactId>derby</artifactId>
			<version>10.10.2.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.derby</groupId>
			<artifactId>derbyclient</artifactId>
			<version>10.10.2.0</version>
		</dependency>

		<dependency>
			<groupId>dom4j</groupId>
			<artifactId>dom4j</artifactId>
			<version>1.6.1</version>
		</dependency>

SQL 建表及數據插入(如果在第一節中作過,可以跳過此步):

CREATE TABLE USER_TEST_TB(
ID INT PRIMARY KEY,
USERNAME VARCHAR(20) NOT NULL,
PASSWORD VARCHAR(20) NOT NULL,
NICKNAME VARCHAR(20) NOT NULL
);

INSERT INTO USER_TEST_TB VALUES(1,'1st','111','Jack');
INSERT INTO USER_TEST_TB VALUES(2,'2nd','222','Rose');
INSERT INTO USER_TEST_TB VALUES(3,'3rd','333','Will');

Mybatis配置文件 src/main/resource源目錄下

test-mybatis-configuration.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
	<properties>
		<property name="driver" value="org.apache.derby.jdbc.ClientDriver" />
		<property name="url"
			value="jdbc:derby://localhost:1527/freud;create=true" />
	</properties>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${driver}" />
				<property name="url" value="${url}" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper class="com.freud.practice.annotation.UserMapper" />
		<mapper resource="com/freud/practice/xml/UserMapper.xml" />
	</mappers>
</configuration>

User.java對象類(src/main/java/com/freud/practice目錄下)

package com.freud.practice;

/**
 * 
 * User Model
 * 
 */
public class User
{

	private String id;

	private String username;

	private String password;

	private String nickname;

	public String getId()
	{
		return id;
	}

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

	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 String getNickname()
	{
		return nickname;
	}

	public void setNickname(String nickname)
	{
		this.nickname = nickname;
	}

}

Select.java 註解類(src/main/java/com/freud/practice/annotation目錄下)

package com.freud.practice.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** 標註此註解只能用在方法上 */
@Target(ElementType.METHOD)
/** 標註此註解生命週期是在Runtime運行時 */
@Retention(RetentionPolicy.RUNTIME)
public @interface Select
{
	String value();
}

UserMapper.java 基於Annotation的配置類(src/main/java/com/freud/practice/annotation目錄下)

package com.freud.practice.annotation;

import com.freud.practice.User;

import java.util.List;

public interface UserMapper
{

	@Select("select * from USER_TEST_TB")
	public List<User> getUser();
}

Mapper.java 對象類(src/main/java/com/freud/practice/simulation目錄下)
package com.freud.practice.simulation;

/**
 * 
 * 存儲查詢結果對象
 *
 */
public class Mapper
{
	/**
	 * 返回類型
	 */
	private String resultType;

	/**
	 * 查詢SQL
	 */
	private String querySql;

	public String getResultType()
	{
		return resultType;
	}

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

	public String getQuerySql()
	{
		return querySql;
	}

	public void setQuerySql(String querySql)
	{
		this.querySql = querySql;
	}

}

SQLSelectProxy.java AOP動態代理類(src/main/java/com/freud/practice/simulation目錄下)

package com.freud.practice.simulation;

import com.freud.practice.annotation.Select;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;

public class SQLSelectProxy implements InvocationHandler
{

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
	{
		/**
		 * 獲得Mapper方法上的Select註解,以此來取得註解中的SQL語句
		 */
		Select select = method.getAnnotation(Select.class);

		if (!method.isAnnotationPresent(Select.class))
		{
			throw new RuntimeException("缺少@Select註解!");
		}

		PreparedStatement pstmt = null;
		ResultSet rs = null;
		Object obj = null;

		try
		{
			pstmt = SqlSessionImpl.connection.prepareStatement(select.value());
			rs = pstmt.executeQuery();

			/**
			 * 獲得Method的返回對象類型,此處應當作判斷處理,當List的時候,當只返回一個對象的時候. 
			 * 爲了簡單實現功能並與第一節中測試文件不發生衝突起見,此處當作List處理
			 */
			String returnType = method.getGenericReturnType().toString();//java.util.List<com.freud.practice.User>

			if (returnType.startsWith(List.class.getName()))
			{
				//去掉我們不需要的字符串,得到List中的類型
				returnType = returnType.replace(List.class.getName(), "").replace("<", "").replace(">", "");
			}
			else
			{
				// 返回其他對象應當作其他處理,此處爲了簡單起見,暫不處理
			}
			obj = SqlSessionImpl.executeQuery(rs, returnType);

		}
		finally
		{
			if (rs != null && !rs.isClosed())
			{
				rs.close();
			}
			if (pstmt != null && !pstmt.isClosed())
			{
				pstmt.close();
			}

		}
		return obj;
	}
}

SqlSession.java Mybatis模擬接口(src/main/java/com/freud/practice/simulation目錄下)

package com.freud.practice.simulation;

import java.util.List;

/**
 * 
 * 模擬SqlSession
 * 
 */
public interface SqlSession
{

	public <T> T getMapper(Class<T> clazz);

	public <E> List<E> selectList(String query) throws Exception;

}

SqlSessionFactory.java Mybatis模擬類(src/main/java/com/freud/practice/simulation目錄下)

package com.freud.practice.simulation;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 * 
 * 模擬SqlSessionFactory
 * 
 */
public class SqlSessionFactory
{

	private InputStream configuration;

	public SqlSession openSession() throws IOException
	{
		SqlSessionImpl session = new SqlSessionImpl();
		loadConfigurations(session);
		return session;
	}

	/**
	 * 
	 * 通過Dom4j讀取配置文件信息
	 * 
	 * @param session
	 * @throws IOException
	 */
	private void loadConfigurations(final SqlSessionImpl session) throws IOException
	{
		try
		{
			Document document = new SAXReader().read(configuration);
			Element root = document.getRootElement();

			List<Element> mappers = root.element("mappers").elements("mapper");
			for (Element mapper : mappers)
			{
				if (mapper.attribute("resource") != null)
				{
					session.setXmlSQLs(loadXMLConfiguration(mapper.attribute("resource").getText()));
				}

				if (mapper.attribute("class") != null)
				{

				}
			}
		}
		catch (Exception e)
		{
			System.out.println("讀取配置文件錯誤!");
		}
		finally
		{
			configuration.close();
		}
	}

	/**
	 * 
	 * 通過dom4j讀取Mapper.xml中的信息
	 * 
	 * @param resource
	 * @return
	 * @throws DocumentException
	 * @throws IOException
	 */
	private Map<String, Mapper> loadXMLConfiguration(String resource) throws DocumentException, IOException
	{
		Map<String, Mapper> map = new HashMap<String, Mapper>();
		InputStream is = null;
		try
		{
			is = this.getClass().getClassLoader().getResourceAsStream(resource);

			Document document = new SAXReader().read(is);
			Element root = document.getRootElement();
			if (root.getName().equalsIgnoreCase("mapper"))
			{

				String namespace = root.attribute("namespace").getText();

				for (Element select : (List<Element>) root.elements("select"))
				{
					Mapper mapperModel = new Mapper();
					mapperModel.setResultType(select.attribute("resultType").getText());
					mapperModel.setQuerySql(select.getText().trim());

					map.put(namespace + "." + select.attribute("id").getText(), mapperModel);
				}
			}
		}
		finally
		{
			is.close();
		}
		return map;
	}

	public InputStream getConfiguration()
	{
		return configuration;
	}

	public void setConfiguration(InputStream configuration)
	{
		this.configuration = configuration;
	}
}

SqlSessionFactoryBuilder.java Mybatis模擬類(src/main/java/com/freud/practice/simulation目錄下)

package com.freud.practice.simulation;

import java.io.InputStream;

/**
 * 
 * 模擬SqlSessionFactoryBuilder
 * 
 */
public class SqlSessionFactoryBuilder
{

	public SqlSessionFactory build(InputStream is)
	{
		SqlSessionFactory sessionFactory = new SqlSessionFactory();
		sessionFactory.setConfiguration(is);
		return sessionFactory;
	}

}

SqlSessionImpl.java Mybatis模擬類(src/main/java/com/freud/practice/simulation目錄下)

package com.freud.practice.simulation;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 
 * 模擬SqlSessionImpl
 * 
 */
public class SqlSessionImpl implements SqlSession
{

	/** DB connection */
	public static Connection connection;

	private Map<String, Mapper> xmlSQLs;

	private List<String> annotationClasses;

	public SqlSessionImpl()
	{
		/**
		 * driverString 和 connString 應該是從配置文件讀取,這裏簡化了
		 */
		final String driverString = "org.apache.derby.jdbc.ClientDriver";
		final String connString = "jdbc:derby://localhost:1527/freud;create=true";
		try
		{
			Class.forName(driverString);
			/** 獲得DB連接 */
			connection = DriverManager.getConnection(connString);
		}
		catch (Exception e)
		{
			System.out.println("獲取DBConnection出錯!");
		}
	}

	/**
	 * 基於Annotation的數據庫操作
	 * 
	 */
	@Override
	public <T> T getMapper(Class<T> clazz)
	{
		T clazzImpl =
		        (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {clazz}, new SQLSelectProxy());

		return clazzImpl;
	}

	/**
	 * 
	 * 基於XML的查詢操作
	 */
	@Override
	public <E> List<E> selectList(String query) throws Exception
	{
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try
		{
			/** 簡單的PreparedStateme JDBC實現 */
			pstmt = connection.prepareStatement(xmlSQLs.get(query).getQuerySql());
			rs = pstmt.executeQuery();
			/** 執行查詢操作 */
			return executeQuery(rs, xmlSQLs.get(query).getResultType());
		}
		finally
		{
			if (!rs.isClosed())
			{
				rs.close();
			}

			if (!pstmt.isClosed())
			{
				pstmt.close();
			}
		}
	}

	/**
	 * 
	 * 執行查詢操作,並將查詢到的結果與配置中的ResultType根據變量名一一對應,通過反射調用Set方法注入各個變量的值
	 * 
	 * @param rs
	 * @param type
	 * @return
	 * @throws Exception
	 */
	public static <E> List<E> executeQuery(ResultSet rs, String type) throws Exception
	{
		int count = rs.getMetaData().getColumnCount();

		List<String> columnNames = new ArrayList<String>();

		for (int i = 1; i <= count; i++)
		{
			columnNames.add(rs.getMetaData().getColumnName(i));
		}

		final List list = new ArrayList<Object>();

		while (rs.next())
		{
			Class modelClazz = Class.forName(type);
			Object obj = modelClazz.newInstance();
			for (Method setMethods : modelClazz.getMethods())
			{
				for (String columnName : columnNames)
				{
					if (setMethods.getName().equalsIgnoreCase("set" + columnName))
					{
						setMethods.invoke(obj, rs.getString(columnName));
					}
				}
			}
			list.add(obj);
		}

		return list;
	}

	public Map<String, Mapper> getXmlSQLs()
	{
		return xmlSQLs;
	}

	public void setXmlSQLs(Map<String, Mapper> xmlSQLs)
	{
		this.xmlSQLs = xmlSQLs;
	}

	public List<String> getAnnotationClasses()
	{
		return annotationClasses;
	}

	public void setAnnotationClasses(List<String> annotationClasses)
	{
		this.annotationClasses = annotationClasses;
	}
}

UserMapper.xml 基於XML的Mapper配置文件(src/main/java/com/freud/practice/xml目錄下)

<?xml version="1.0" encoding="UTF-8" ?>
  <!-- namespace 當基於XML進行配置的時候是根據namespace+id來拼接進行SQL操作 -->
<mapper namespace="com.freud.practice.UserMapper">
	<!-- select 查詢 -->
	<select id="getUser" resultType="com.freud.practice.User">
		select *
		from USER_TEST_TB
	</select>
</mapper>

TestMyBatis.java 測試類(src/test/java/com/freud/practice目錄下)

package com.freud.practice;

import com.freud.practice.annotation.UserMapper;
import com.freud.practice.simulation.SqlSession;
import com.freud.practice.simulation.SqlSessionFactory;
import com.freud.practice.simulation.SqlSessionFactoryBuilder;
import com.freud.practice.simulation.SqlSessionImpl;

import java.io.InputStream;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class TestMyBatis
{

	/** 配置置文件 */
	private String source;

	private InputStream inputStream;

	private SqlSessionFactory sqlSessionFactory;

	@Before
	public void setUp()
	{
		source = "test-mybatis-configuration.xml";
	}

	/**
	 * 
	 * 基於XML格式配置的測試方法
	 * 
	 */
	@Test
	public void testXMLConfingure()
	{

		try
		{
			/**
			 * 獲得Session
			 */
			inputStream = TestMyBatis.class.getClassLoader().getResourceAsStream(source);
			sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
			SqlSession session = sqlSessionFactory.openSession();

			/**
			 * 執行Query操作
			 */
			List<User> users = (List) session.selectList("com.freud.practice.UserMapper.getUser");
			System.out.println("Query by XML configuration...");

			/**
			 * 打印結果
			 */
			this.printUsers(users);

		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	/**
	 * 
	 * 基於Annotation配置的測試方法
	 * 
	 */
	@Test
	public void testAnnotationConfingure()
	{
		try
		{
			inputStream = TestMyBatis.class.getClassLoader().getResourceAsStream(source);
			sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
			SqlSession session = sqlSessionFactory.openSession();

			UserMapper userMapper = session.getMapper(UserMapper.class);
			System.out.println("\r\nQuery by annotation configuration...");
			this.printUsers(userMapper.getUser());

		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	@After
	public void clearUp() throws SQLException
	{
		if (SqlSessionImpl.connection != null && !SqlSessionImpl.connection.isClosed())
		{
			SqlSessionImpl.connection.close();
		}
	}

	private void printUsers(final List<User> users)
	{
		int count = 0;

		for (User user : users)
		{
			System.out.println(MessageFormat.format("==User[{0}]=================", ++count));
			System.out.println("User Id: " + user.getId());
			System.out.println("User UserName: " + user.getUsername());
			System.out.println("User Password: " + user.getPassword());
			System.out.println("User nickname: " + user.getNickname());
		}
	}
	
}

以上就是基於XML以及Annotation的方式對Mybatis實現了一個簡單的模擬。旨在理解Mybatis的工作原理。

筆者一直覺得當學習一個工具類技術的時候,路線應該是

1.實現一個小例子

2.找材料理解其中原理

3.學習技術細節,並動手全部實現

4.在全部學完之後動手做一個小項目,儘可能的使用這個在技術中的各個環節。


這種方法對筆者來說屢試不爽。

現在我們已經走完了前兩步,接下來就是學習技術細節了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章