天天看點

自定義mybatis架構

  • 準備工作:

1.建立 Maven 工程

Groupid:com.atguigu 

ArtifactId:mybatisMyself 

Packing:jar

2.引入相關坐标

<dependencies>

<dependency>

<groupId>log4j</groupId>

<artifactId>log4j</artifactId>

<version>1.2.12</version>

</dependency>

<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.6</version>

</dependency>

<dependency>

<groupId>jaxen</groupId>

<artifactId>jaxen</artifactId>

<version>1.1.6</version>

</dependency>

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.10</version>

</dependency>

</dependencies>

3.引入工具類到項目中

public class XMLConfigBuilder {

public static void loadConfiguration(DefaultSqlSession session,InputStream config){

try{

//定義封裝連接配接資訊的配置對象(mybatis 的配置對象)

Configuration cfg = new Configuration();

//1.擷取 SAXReader 對象

SAXReader reader = new SAXReader();

//2.根據位元組輸入流擷取 Document 對象

Document document = reader.read(config);

//3.擷取根節點

Element root = document.getRootElement();

//4.使用 xpath 中選擇指定節點的方式,擷取所有 property 節點

List propertyElements = root.selectNodes("//property");

//5.周遊節點

for(Element propertyElement : propertyElements){

//判斷節點是連接配接資料庫的哪部分資訊

//取出 name 屬性的值

String name = propertyElement.attributeValue("name");

if("driver".equals(name)){

//表示驅動

//擷取 property 标簽 value 屬性的值

String driver = propertyElement.attributeValue("value");

cfg.setDriver(driver);

}

if("url".equals(name)){

//表示連接配接字元串

//擷取 property 标簽 value 屬性的值

String url = propertyElement.attributeValue("value");

cfg.setUrl(url);

}

if("username".equals(name)){

//表示使用者名

//擷取 property 标簽 value 屬性的值

String username = propertyElement.attributeValue("value");

cfg.setUsername(username);

}

if("password".equals(name)){

//表示密碼

//擷取 property 标簽 value 屬性的值

String password = propertyElement.attributeValue("value");

cfg.setPassword(password);

}

}

//取出 mappers 中的所有 mapper 标簽,判斷他們使用了 resource 還是 class 屬性

List mapperElements = root.selectNodes("//mappers/mapper");

//周遊集合

for(Element mapperElement : mapperElements){

//判斷 mapperElement 使用的是哪個屬性

Attribute attribute = mapperElement.attribute("resource");

if(attribute != null){

System.out.println("使用的是 XML");

//表示有 resource 屬性,用的是 XML

//取出屬性的值

String mapperPath = attribute.getValue();// 獲 取 屬 性 的 值

"com/itheima/dao/IUserDao.xml"

//把映射配置檔案的内容擷取出來,封裝成一個 map

Map<String,Mapper> mappers = loadMapperConfiguration(mapperPath);

//給 configuration 中的 mappers 指派

cfg.setMappers(mappers);

}else{

System.out.println("使用的是注解");

//表示沒有 resource 屬性,用的是注解

//擷取 class 屬性的值

String daoClassPath = mapperElement.attributeValue("class");

//根據 daoClassPath 擷取封裝的必要資訊

Map<String,Mapper> mappers = loadMapperAnnotation(daoClassPath);

//給 configuration 中的 mappers 指派

cfg.setMappers(mappers);

}

}

//把配置對象傳遞給 DefaultSqlSession

session.setCfg(cfg);

}catch(Exception e){

throw new RuntimeException(e);

}finally{

try {

config.close();

}catch(Exception e){

e.printStackTrace();

}

}

}

private static Map<String,Mapper> loadMapperConfiguration(String

mapperPath)throws IOException {

InputStream in = null;

try{

//定義傳回值對象

Map<String,Mapper> mappers = new HashMap<String,Mapper>();

//1.根據路徑擷取位元組輸入流

in = Resources.getResourceAsStream(mapperPath);

//2.根據位元組輸入流擷取 Document 對象

SAXReader reader = new SAXReader();

Document document = reader.read(in);

//3.擷取根節點

Element root = document.getRootElement();

//4.擷取根節點的 namespace 屬性取值

String namespace = root.attributeValue("namespace");//是組成 map 中 key 的

部分

//5.擷取所有的 select 節點

List selectElements = root.selectNodes("//select");

//6.周遊 select 節點集合

for(Element selectElement : selectElements){

//取出 id 屬性的值 組成 map 中 key 的部分

String id = selectElement.attributeValue("id");

//取出 resultType 屬性的值 組成 map 中 value 的部分

String resultType = selectElement.attributeValue("resultType");

//取出文本内容 組成 map 中 value 的部分

String queryString = selectElement.getText();

//建立 Key

String key = namespace+"."+id;

//建立 Value

Mapper mapper = new Mapper();

mapper.setQueryString(queryString);

mapper.setResultType(resultType);

//把 key 和 value 存入 mappers 中

mappers.put(key,mapper);

}

return mappers;

}catch(Exception e){

throw new RuntimeException(e);

}finally{

in.close();

}

}

private static Map<String,Mapper> loadMapperAnnotation(String

daoClassPath)throws Exception{

//定義傳回值對象

Map<String,Mapper> mappers = new HashMap<String, Mapper>();

//1.得到 dao 接口的位元組碼對象

Class daoClass = Class.forName(daoClassPath);

//2.得到 dao 接口中的方法數組

Method[] methods = daoClass.getMethods();

//3.周遊 Method 數組

for(Method method : methods){

//取出每一個方法,判斷是否有 select 注解

boolean isAnnotated = method.isAnnotationPresent(Select.class);

if(isAnnotated){

//建立 Mapper 對象

Mapper mapper = new Mapper();

//取出注解的 value 屬性值

Select selectAnno = method.getAnnotation(Select.class);

String queryString = selectAnno.value();

mapper.setQueryString(queryString);

//擷取目前方法的傳回值,還要求必須帶有泛型資訊

Type type = method.getGenericReturnType();//List

//判斷 type 是不是參數化的類型

if(type instanceof ParameterizedType){

//強轉

ParameterizedType ptype = (ParameterizedType)type;

//得到參數化類型中的實際類型參數

Type[] types = ptype.getActualTypeArguments();

//取出第一個

Class domainClass = (Class)types[0];

//擷取 domainClass 的類名

String resultType = domainClass.getName();

//給 Mapper 指派

mapper.setResultType(resultType);

}

//組裝 key 的資訊

//擷取方法的名稱

String methodName = method.getName();

String className = method.getDeclaringClass().getName();

String key = className+"."+methodName;

//給 map 指派

mappers.put(key,mapper);

}

}

return mappers;

}

}

//負責執行 SQL 語句,并且封裝結果集

public class Executor {

public List selectList(Mapper mapper, Connection conn) {

PreparedStatement pstm = null;

ResultSet rs = null;

try {

//1.取出 mapper 中的資料

String queryString = mapper.getQueryString();//select * from user

String resultType = mapper.getResultType();//com.atguigu.domain.User

Class domainClass = Class.forName(resultType);//User.class

//2.擷取 PreparedStatement 對象

pstm = conn.prepareStatement(queryString);

//3.執行 SQL 語句,擷取結果集

rs = pstm.executeQuery();

//4.封裝結果集

List list = new ArrayList();//定義傳回值

while(rs.next()) {

//執行個體化要封裝的實體類對象

E obj = (E)domainClass.newInstance();//User 對象

//取出結果集的元資訊:ResultSetMetaData

ResultSetMetaData rsmd = rs.getMetaData();

//取出總列數

int columnCount = rsmd.getColumnCount();

//周遊總列數

for (int i = 1; i <= columnCount; i++) {

//擷取每列的名稱,列名的序号是從 1 開始的

String columnName = rsmd.getColumnName(i);

//根據得到列名,擷取每列的值

Object columnValue = rs.getObject(columnName);

//給 obj 指派:使用 Java 内省機制(借助 PropertyDescriptor 實作屬性的封裝)

PropertyDescriptor pd = new

PropertyDescriptor(columnName,domainClass);//要求:實體類的屬性和資料庫表的列名保持一種

Method writeMethod = pd.getWriteMethod();//setUsername(String

username);

//把擷取的列的值,給對象指派

writeMethod.invoke(obj,columnValue);

}

//把賦好值的對象加入到集合中

list.add(obj);

}

return list;

} catch (Exception e) {

throw new RuntimeException(e);

} finally {

release(pstm,rs);

}

}

private void release(PreparedStatement pstm,ResultSet rs){

if(rs != null){

try {

rs.close();

}catch(Exception e){

e.printStackTrace();

}

}

if(pstm != null){

try {

pstm.close();

}catch(Exception e){

e.printStackTrace();

}

}

}

}

//資料源的工具類

public class DataSourceUtil {

//擷取連接配接

public static Connection getConnection(Configuration cfg) {

try {

Class.forName(cfg.getDriver());

Connection conn =

DriverManager.getConnection(cfg.getUrl(),cfg.getUsername() , cfg.getPassword());

return conn;

} catch (Exception e) {

throw new RuntimeException(e);

}

}

}

4.編寫 SqlMapConfig.xml

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

<configuration>

<environments default="development">

<environment id="development">

<transactionManager type="JDBC" />

<dataSource type="POOLED">

<property name="driver" value="com.mysql.jdbc.Driver" ></property>

<property name="url" value="jdbc:mysql:///eesy" ></property>

<property name="username" value="root"></property>

<property name="password" value="1234"></property>

</dataSource>

</environment>

</environments>

</configuration>

5.編寫讀取配置檔案類

public class Resources

public static InputStream getResourceAsStream(String xmlPath) {

return Resources.class.getClassLoader().getResourceAsStream(xmlPath); } }

6.編寫 Mapper 類

//用于封裝查詢時的必要資訊:要執行的 SQL 語句和實體類的全限定類名

public class Mapper {

private String queryString;//sql

private String resultType;//結果類型的全限定類名

public String getQueryString() {

return queryString;

}

public void setQueryString(String queryString) {

this.queryString = queryString;

}

public String getResultType() {

return resultType;

}

public void setResultType(String resultType) {

this.resultType = resultType;

}

}

7.編寫 Configuration 配置類

//核心配置類 --1.資料庫資訊 ;2.sql 的 map 集合

public class Configuration {

private String username; //使用者名

private String password;//密碼

private String url;//位址

private String driver;//驅動

//map 集合 Map<唯一辨別,Mapper> 用于儲存映射檔案中的 sql 辨別及 sql 語句

private Map<String,Mapper> mappers;

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 getUrl() {

return url;

}

public void setUrl(String url) {

this.url = url;

}

public String getDriver() {

return driver;

}

public void setDriver(String driver) {

this.driver = driver;

}

public Map<String, Mapper> getMappers() {

return mappers;

}

public void setMappers(Map<String, Mapper> mappers) {

this.mappers = mappers;

}

}

8.編寫 User 實體類

public class User implements Serializable {

private int id;

private String username;// 使用者姓名

private String sex;// 性别

private Date birthday;// 生日

private String address;// 位址

//省略 getter 與 setter

@Override

public String toString() {

return "User [id=" + id + ", username=" + username + ", sex=" + sex

+ ", birthday=" + birthday + ", address=" + address + "]";

} }

  • 基于 XML 的自定義 mybatis 架構

1.編寫持久層接口和 IUserDao.xml

public interface IUserDao {

List findAll();

}

對應mapper.xml配置

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

<mapper namespace="com.atguigu.dao.IUserDao">

<select id="findAll" resultType="com.atguigu.domain.User">

select * from user

</select>

</mapper>

注意:

此處我們使用的也是 mybatis 的配置檔案,是以也要把限制删除了

2.編寫建構者類

//用于建構 SqlSessionFactory

public class SqlSessionFactoryBuilder {

public SqlSessionFactory build(InputStream in) {

DefaultSqlSessionFactory factory = new DefaultSqlSessionFactory();

//給 factory 中 config 指派

factory.setConfig(in);

return factory;

}

}

3.編寫 SqlSession 接口和實作類

//操作資料庫的核心對象

public interface SqlSession {

T getMapper(Class daoClass);

// 釋放資源  

void close();

}

//Description: SqlSession 的具體實作

public class DefaultSqlSession implements SqlSession {

//核心配置對象

private Configuration cfg;

public void setCfg(Configuration cfg) {

this.cfg = cfg;

}

//連接配接對象

private Connection conn;

//調用 DataSourceUtils 工具類擷取連接配接

public Connection getConn() {

try {

conn = DataSourceUtil.getDataSource(cfg).getConnection();

return conn;

} catch (Exception e) {

throw new RuntimeException(e);

}

}

@Override

public T getMapper(Class daoClass) {

conn = getConn();

System.out.println(conn);

T daoProxy = (T) Proxy.newProxyInstance(daoClass.getClassLoader(),new

Class[] {daoClass}, new MapperProxyFactory(cfg.getMappers(),conn));

return daoProxy;

}

//釋放資源

@Override

public void close() {

try {

System.out.println(conn);

conn.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

//查詢所有方法

public List selectList(String statement){

Mapper mapper = cfg.getMappers().get(statement);

return new Executor().selectList(mapper,conn);

}

}

  1. 編寫用于建立 Dao 接口代理對象的類

//用于建立代理對象是增強方法

public class MapperProxyFactory implements InvocationHandler {

private Map<String,Mapper> mappers;

private Connection conn;

public MapperProxyFactory(Map<String, Mapper> mappers,Connection conn) {

this.mappers = mappers;

this.conn = conn;

}

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

{

//1.取出方法名

String methodName = method.getName();

//2.取出方法所在類名

String className = method.getDeclaringClass().getName();

//3.拼接成 Key

String key = className+"."+methodName;

//4.使用 key 取出 mapper

Mapper mapper = mappers.get(key);

if(mapper == null) {

throw new IllegalArgumentException("傳入的參數有誤,無法擷取執行的必要條件

");

}

//5.建立 Executor 對象

Executor executor = new Executor();

return executor.selectList(mapper, conn);

}

}

  1. 運作測試類

//測試 mybatis 的環境

public class MybatisTest {

public static void main(String[] args)throws Exception {

//1.讀取配置檔案

InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

//2.建立 SqlSessionFactory 的建構者對象

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

//3.使用建構者建立工廠對象 SqlSessionFactory

SqlSessionFactory factory = builder.build(in);

//4.使用 SqlSessionFactory 生産 SqlSession 對象

SqlSession session = factory.openSession();

//5.使用 SqlSession 建立 dao 接口的代理對象

IUserDao userDao = session.getMapper(IUserDao.class);

//6.使用代理對象執行查詢所有方法

List users = userDao.findAll();

for(User user : users) {

System.out.println(user);

}

//7.釋放資源

session.close();

in.close();

}

}

  • 基于注解方式定義 Mybatis 架構
  1. 自定義@Select 注解

//自定義查詢注解

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface Select {

String value();

}

  1. 修改持久層接口

 public interface IUserDao {

// 查詢所有使用者

@Select("select * from user")

List findAll();

}

  1. 修改 SqlMapConfig.xml

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

<configuration>

<environments default="mysql">

<environment id="mysql">

<transactionManager type="JDBC"></transactionManager>

<dataSource type="POOLED">

<property name="driver" value="com.mysql.jdbc.Driver"/>

<property name="url" value="jdbc:mysql://localhost:3306/ee50"/>

<property name="username" value="root"/>

<property name="password" value="1234"/>

</dataSource>

</environment>

</environments>

<mappers>

"com.atguigu.dao.IUserDao"/>

</mappers>

</configuration>

繼續閱讀