- 準備工作:
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);
}
}
- 編寫用于建立 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);
}
}
- 運作測試類
//測試 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 架構
- 自定義@Select 注解
//自定義查詢注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
String value();
}
- 修改持久層接口
public interface IUserDao {
// 查詢所有使用者
@Select("select * from user")
List findAll();
}
- 修改 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>