20160522--20160526----mybatis入门基础
一、基礎知識:
?1.對原生態jdbc程序(單獨使用jdbc開發)問題總結
?2.mybatis框架原理 (掌握)
?3.mybatis入門程序
?4.用戶的增、刪、改、查
?5.SqlMapConfig.xml
?6.輸入映射
?7.輸出映射
?8.動態sql
?
1.對原生態jdbc程序中問題總結
1.1?環境
java環境:jdk1.8.0_20
eclipse:luna
mysql:5.1
1.2 ? 創建mysql數據
sql腳本:
/* SQLyog v10.2 MySQL - 5.1.72-community : Database - mybatis ********************************************************************* *//*!40101 SET NAMES utf8 */;/*!40101 SET SQL_MODE=''*/;/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; /*Table structure for table `items` */CREATE TABLE `items` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(32) NOT NULL COMMENT '商品名稱',`price` float(10,1) NOT NULL COMMENT '商品定價',`detail` text COMMENT '商品描述',`pic` varchar(64) DEFAULT NULL COMMENT '商品圖片',`createtime` datetime NOT NULL COMMENT '生產日期',PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;/*Table structure for table `orderdetail` */CREATE TABLE `orderdetail` (`id` int(11) NOT NULL AUTO_INCREMENT,`orders_id` int(11) NOT NULL COMMENT '訂單id',`items_id` int(11) NOT NULL COMMENT '商品id',`items_num` int(11) DEFAULT NULL COMMENT '商品購買數量',PRIMARY KEY (`id`),KEY `FK_orderdetail_1` (`orders_id`),KEY `FK_orderdetail_2` (`items_id`),CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`items_id`) REFERENCES `items` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;/*Table structure for table `orders` */CREATE TABLE `orders` (`id` int(11) NOT NULL AUTO_INCREMENT,`user_id` int(11) NOT NULL COMMENT '下單用戶id',`number` varchar(32) NOT NULL COMMENT '訂單號',`createtime` datetime NOT NULL COMMENT '創建訂單時間',`note` varchar(100) DEFAULT NULL COMMENT '備注',PRIMARY KEY (`id`),KEY `FK_orders_1` (`user_id`),CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;/*Table structure for table `user` */CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(32) NOT NULL COMMENT '用戶名稱',`birthday` date DEFAULT NULL COMMENT '生日',`sex` char(1) DEFAULT NULL COMMENT '性別',`address` varchar(256) DEFAULT NULL COMMENT '地址',PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; sql腳本1.3?jdbc程序
使用jdbc查詢mysql數據庫中用戶表的記錄。
創建java工程,加入jar包:
數據庫驅動包(mysql5.1)
?
上邊的是mysql驅動。
下邊的是oracle的驅動。
?程序代碼:
package com.dzq.mybatis.jdbc;import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException;/*** 通過Jdbc程序,總結單獨的jdbc程序,總結其中* * @author 小強**/ public class JdbcTest {public static void main(String[] args) {// 數據庫連接Connection connection = null;// 預編譯的Statement,可以提高數據庫的性能PreparedStatement preparedStatement = null;// 結果集對象ResultSet resultSet = null;try {// 加載數據庫驅動Class.forName("com.mysql.jdbc.Driver");// 通過驅動管理類獲取數據庫鏈接connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","root", "mysql");// 定義sql語句 ?表示占位符String sql = "select * from user where username = ?";// 獲取預處理statementpreparedStatement = connection.prepareStatement(sql);// 設置參數,第一個參數為sql語句中參數的序號(從1開始),第二個參數為設置的參數值preparedStatement.setString(1, "王五");// 向數據庫發出sql執行查詢,查詢出結果集resultSet = preparedStatement.executeQuery();// 遍歷查詢結果集while (resultSet.next()) {System.out.println(resultSet.getString("id") + " "+ resultSet.getString("username"));}} catch (Exception e) {e.printStackTrace();} finally {// 釋放資源if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {// TODO Auto-generated catch block e.printStackTrace();}}if (preparedStatement != null) {try {preparedStatement.close();} catch (SQLException e) {// TODO Auto-generated catch block e.printStackTrace();}}if (connection != null) {try {connection.close();} catch (SQLException e) {// TODO Auto-generated catch block e.printStackTrace();}}}}} JdbcTest1.4 ?問題總結
1、數據庫連接,使用時就創建,不使用立即釋放,對數據庫進行頻繁連接開啟和關閉,造成數據庫資源浪費,影響 數據庫性能。
設想:使用數據庫連接池管理數據庫連接。
2、將sql語句硬編碼到java代碼中,如果sql 語句修改,需要重新編譯java代碼,不利于系統維護。
設想:將sql語句配置在xml配置文件中,即使sql變化,不需要對java代碼進行重新編譯。
3、向preparedStatement中設置參數,對占位符號位置和設置參數值,硬編碼在java代碼中,不利于系統維護。
設想:將sql語句及占位符號和參數全部配置在xml中。
4、從resultSet中遍歷結果集數據時,存在硬編碼,將獲取表的字段進行硬編碼,,不利于系統維護。
設想:將查詢的結果集,自動映射成java對象。
2.mybatis框架
2.1???? mybatis是什么?
mybatis是一個持久層的框架,是apache下的頂級項目。
mybatis托管到goolecode下,再后來托管到github下(https://github.com/mybatis/mybatis-3/releases)。
mybatis讓程序將主要精力放在sql上,通過mybatis提供的映射方式,自由靈活生成(半自動化,大部分需要程序員編寫sql)滿足需要sql語句。
mybatis可以將向 preparedStatement中的輸入參數自動進行輸入映射,將查詢結果集靈活映射成java對象。(輸出映射)
2.2 ? ?mybatis框架
3入門程序
3.1?需求
根據用戶id(主鍵)查詢用戶信息
根據用戶名稱模糊查詢用戶信息
添加用戶
刪除 用戶
更新用戶
?
3.2?環境
java環境:jdk1.8.0_20
eclipse:luna
mysql:5.1
jar包:mybatis-3.2.7.jar
lib下:依賴包
mybatis-3.2.7.jar:核心 包
mybatis-3.2.7.pdf,操作指南
加入mysql的驅動包
3.3?log4j.properties
?
# Global logging configuration #\u5728\u5f00\u53d1\u73af\u5883\u4e0b\u65e5\u5fd7\u7ea7\u522b\u8981\u8bbe\u7f6e\u6210DEBUG\uff0c\u751f\u4ea7\u73af\u5883\u8bbe\u7f6e\u6210info\u6216error log4j.rootLogger=DEBUG, stdout # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n3.4工程結構
?
3.5?SqlMapConfig.xml
配置mybatis的運行環境,數據源、事務等。
?
<?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><!-- 和spring整合后 environments配置將廢除--><environments default="development"><environment id="development"><!-- 使用jdbc事務管理,事務控制由mybatis--><transactionManager type="JDBC" /><!-- 數據庫連接池,由mybatis管理--><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}" /><property name="url" value="${jdbc.url}" /><property name="username" value="${jdbc.username}" /><property name="password" value="${jdbc.password}" /></dataSource></environment></environments></configuration> SqlMapConfig.xml?
3.6根據用戶id(主鍵)查詢用戶信息
?
3.6.1?創建po類
?
package com.dzq.mybatis.domain;import java.util.Date;public class User {// 屬性名和數據庫表的字段對應private int id;private String username;// 用戶姓名private String sex;// 性別private Date birthday;// 生日private String address;// 地址public int getId() {return id;}public void setId(int id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}} User.java?
3.6.2 ? ?映射文件
映射文件命名:
User.xml(原始ibatis命名),mapper代理開發映射文件名稱叫XXXMapper.xml,比如:UserMapper.xml、ItemsMapper.xml
在映射文件中配置sql語句。
<?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"> <!--命名空間,對sql進行分類管理,實現sql隔離 注意:使用mapper代理的方法開發,namespace就有特殊重要的作用 --> <mapper namespace="test"><!-- 在映射文件中配置很多sql語句 --><!-- 通過id查詢用戶表的記錄 --><!--通過select執行數據庫的查詢 id:標識映射文件中的sql,稱為statemen的id 將sql語句封裝到mappedstatement對象中#{}:表示占位符parameterType:指定輸入參數類型,這里指定int型{id}:其中的id表示接收輸入參數,如果輸入參數是簡單類型,#{}中參數名可以任意,可以是value或者其他名稱resultType:指定sql輸出結果所映射的java對象類型,select指定resultType將單條記錄所映射成的java對象--><select id="findUserById" parameterType="int" resultType="com.dzq.mybatis.domain.User">select * from user where id=#{id}</select> </mapper> User.xml?
3.6.3 ? ? 在SqlMapConfig.xml加載映射文件
在sqlMapConfig.xml中加載User.xml:
<!-- 加載 映射文件 --><mappers><mapper resource="sqlmap/User.xml"/></mappers>3.6.4 ? ? 程序編寫
package com.dzq.mybatis.first;import java.io.IOException; import java.io.InputStream;import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder;import com.dzq.mybatis.domain.User;public class MybatisFirst {// 根據id查詢用戶的信息,得到一條記錄結果public void findUserById(int id) throws IOException {//mybatis配置文件String resource="SqlMapConfig.xml";//得到配置文件流InputStream inputStream=Resources.getResourceAsStream(resource);//創建會話工廠,傳入mybatis的配置文件的信息SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);//通過工廠得到sqlsessionSqlSession sqlSession=sqlSessionFactory.openSession();//通過sqlsession操作數據庫//第一個參數:statement,映射文件中statement的id; 等于namespace+"."statement的id//第二個參數: parameter 指定和映射文件中parameter所匹配的parameterType的類型的 參數//sqlSession.selectOne結果是與映射文件中所匹配的resultType類型對象User user=sqlSession.selectOne("test.findUserById", 1);System.out.println(user);//釋放資源 sqlSession.close();} } MybatisFirst.java?
3.7 ?根據用戶名稱模糊查詢用戶信息
3.7.1???? 映射文件
使用User.xml,添加根據用戶名稱模糊查詢用戶信息的sql語句。
<!-- 根據用戶名稱模糊查詢用戶信息 --><!-- resultType:單條記錄所映射的java對象類型${}:拼接sql串,將接收到的參數不加任何修飾拼接到sql語句中使用${}拼接sql,會引起sql注入${value}表示輸入參數的內容,如果傳入的類型是簡單類型${}只能使用value--><select id="findUserByUserName" parameterType="java.lang.String" resultType="com.dzq.mybatis.domain.User">select * from user where username like '%${value}%'</select>3.7.2 ? 程序代碼
//根據用戶名稱模糊查詢用戶信息public void findUserByUserName() throws IOException{//mybatis配置文件String resource="SqlMapConfig.xml";//得到配置文件流InputStream inputStream=Resources.getResourceAsStream(resource);//創建會話工廠,傳入mybatis的配置文件的信息SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);//通過工廠得到sqlsessionSqlSession sqlSession=sqlSessionFactory.openSession();//通過sqlsession操作數據庫//第一個參數:statement,映射文件中statement的id; 等于namespace+"."statement的id//第二個參數: parameter 指定和映射文件中parameter所匹配的parameterType的類型的 參數//sqlSession.selectList結果是與映射文件中所匹配的resultType類型對象List<User> list=sqlSession.selectList("test.findUserByUserName", "小明");System.out.println(list);//釋放資源 sqlSession.close();}3.8添加用戶
3.8.1映射文件
在 User.xml中配置添加用戶的Statement
<!-- 添加用戶 --><!-- parameterType:輸入參數類型是pojo(包括用戶信息)#{}:指定pojo的屬性名,接收到pojo的屬性值,mybatis通過ognl獲取屬性值--><insert id="addUser" parameterType="com.dzq.mybatis.domain.User">insert into user(id,username,birthday,sex,address) value(#{id},#{username},#{birthday},#{sex},#{address})</insert>?
3.8.2程序代碼
//添加用戶信息 @Testpublic void addUser() throws IOException {// mybatis配置文件String resource = "SqlMapConfig.xml";// 得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);// 創建會話工廠,傳入mybatis的配置文件的信息SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 通過工廠得到sqlsessionSqlSession sqlSession = sqlSessionFactory.openSession();//插入用戶對象User user=new User();user.setUsername("賤賤");user.setSex("男");user.setAddress("山東臨沂");user.setBirthday(new Date());sqlSession.insert("test.addUser", user);//提交事務 sqlSession.commit();// 釋放資源 sqlSession.close();}3.8.3 ? ?自增主鍵返回
mysql自增主鍵,執行insert提交之前自動生成一個自增主鍵。
通過mysql函數獲取到剛插入記錄的自增主鍵:
LAST_INSERT_ID()
是insert之后調用此函數。
修改insertUser定義:
<insert id="addUser" parameterType="com.dzq.mybatis.domain.User"><!-- insert 插入數據返回到User對象中 select last_insert_id:得到剛插入進去數據的主鍵值,只適用于自增主鍵keyProperty:將要查詢到的主鍵值設置到parameterType對象的那個屬性order:相對于insert語句的執行順序--><selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">select last_insert_id()</selectKey>insert into user(username,birthday,sex,address) value(#{username},#{birthday},#{sex},#{address})</insert>3.8.14 ? ?非自增主鍵返回(使用uuid())
使用mysql的uuid()函數生成主鍵,需要修改表中id字段類型為string,長度設置成35位。
執行思路:
先通過uuid()查詢到主鍵,將主鍵輸入 到sql語句中。
執行uuid()語句順序相對于insert語句之前執行。
?
通過oracle的序列生成主鍵:
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User"> <selectKey resultType="java.lang.Integer" order="BEFORE" keyProperty="id"> SELECT 自定義序列.NEXTVAL FROM DUAL </selectKey> insert into user(id,username,birthday,sex,address) values(#{id},#{username},#{birthday},#{sex},#{address}) </insert>注意這里使用的order是“BEFORE”
3.9刪除用戶
3.9.1映射文件
<!-- 刪除用戶根據id刪除用戶,需要輸入id值--><delete id="deleteUser" parameterType="int" >delete from user where id=#{id}</delete>3.9.2程序代碼
//刪除用戶信息 @Testpublic void deleteUser() throws IOException {// mybatis配置文件String resource = "SqlMapConfig.xml";// 得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);// 創建會話工廠,傳入mybatis的配置文件的信息SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 通過工廠得到sqlsessionSqlSession sqlSession = sqlSessionFactory.openSession();//傳入id,根據id刪除用戶sqlSession.delete("test.deleteUser", 33);//提交事務 sqlSession.commit();// 釋放資源 sqlSession.close();}?
3.10更新用戶
3.10.1映射文件
<!-- 更新用戶 分析:需要傳入用戶的id用戶的更新信息parameterType,指定user對象,包括id和更新信息,注意,id必須存在#{id}接收user里的屬性值--><update id="updateUser" parameterType="com.dzq.mybatis.domain.User">update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}</update>3.10.2程序代碼
// 更新用戶信息 @Testpublic void updateUser() throws IOException {// mybatis配置文件String resource = "SqlMapConfig.xml";// 得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);// 創建會話工廠,傳入mybatis的配置文件的信息SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 通過工廠得到sqlsessionSqlSession sqlSession = sqlSessionFactory.openSession();User user = new User();user.setId(32);user.setUsername("賤賤賤");user.setSex("女");user.setAddress("山東萊蕪");user.setBirthday(new Date());// 傳入user對象,根據id更新用戶sqlSession.update("test.updateUser", user);// 提交事務 sqlSession.commit();// 釋放資源 sqlSession.close();}?
3.11小結
?3.11.1 ? ? ?#{}和${}
#{}表示一個占位符號,#{}接收輸入參數,類型可以是簡單類型,pojo、hashmap。
如果接收簡單類型,#{}中可以寫成value或其它名稱。
#{}接收pojo對象值,通過OGNL讀取對象中的屬性值,通過屬性.屬性.屬性...的方式獲取對象屬性值。
${}表示一個拼接符號,會引用sql注入,所以不建議使用${}。
${}接收輸入參數,類型可以是簡單類型,pojo、hashmap。
如果接收簡單類型,${}中只能寫成value。
${}接收pojo對象值,通過OGNL讀取對象中的屬性值,通過屬性.屬性.屬性...的方式獲取對象屬性值。
3.11.2 ? ? parameterType和resultType
parameterType:指定輸入參數類型,mybatis通過ognl從輸入對象中獲取參數值拼接在sql中。
resultType:指定輸出結果類型,mybatis將sql查詢結果的一行記錄數據映射為resultType指定類型的對象。
3.11.3 ? ? selectOne和selectList
selectOne查詢一條記錄,如果使用selectOne查詢多條記錄則拋出異常:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3
??? at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:70)
selectList可以查詢一條或多條記錄。
3.12 ? mybatis和hibernate本質區別和應用場景
hibernate:是一個標準ORM框架(對象關系映射)。入門門檻較高的,不需要程序寫sql,sql語句自動生成了。
對sql語句進行優化、修改比較困難的。
應用場景:
???????? 適用與需求變化不多的中小型項目,比如:后臺管理系統,erp、orm、oa。。
mybatis:專注是sql本身,需要程序員自己編寫sql語句,sql修改、優化比較方便。mybatis是一個不完全 的ORM框架,雖然程序員自己寫sql,mybatis 也可以實現映射(輸入映射、輸出映射)。
應用場景:
???????? 適用與需求變化較多的項目,比如:互聯網項目。
企業進行技術選型,以低成本 高回報作為技術選型的原則,根據項目組的技術力量進行選擇。
4 ? ? mybatis開發dao的方法
4.1???? SqlSession使用范圍
4.1.1???? SqlSessionFactoryBuilder
?通過SqlSessionFactoryBuilder創建會話工廠SqlSessionFactory
將SqlSessionFactoryBuilder當成一個工具類使用即可,不需要使用單例管理SqlSessionFactoryBuilder。
在需要創建SqlSessionFactory時候,只需要new一次SqlSessionFactoryBuilder即可。
4.1.2 ??SqlSessionFactory
通過SqlSessionFactory創建SqlSession,使用單例模式管理sqlSessionFactory(工廠一旦創建,使用一個實例)。
將來mybatis和spring整合后,使用單例模式管理sqlSessionFactory。
?
4.1.3 ? ?SqlSession
SqlSession是一個面向用戶(程序員)的接口。
SqlSession中提供了很多操作數據庫的方法:如:selectOne(返回單個對象)、selectList(返回單個或多個對象)、。
SqlSession是線程不安全的,在SqlSesion實現類中除了有接口中的方法(操作數據庫的方法)還有數據域屬性。
SqlSession最佳應用場合在方法體內,定義成局部變量使用。
4.2 ? ?原始dao開發方法(程序員需要寫dao接口和dao實現類)
4.2.1???? 思路
程序員需要寫dao接口和dao實現類。
需要向dao實現類中注入SqlSessionFactory,在方法體內通過SqlSessionFactory創建SqlSession
4.2.2 ? ? dao接口
package com.dzq.mybatis.dao;import com.dzq.mybatis.domain.User;public interface UserDao {// 根據id查詢用戶信息public User findUserById(int id) throws Exception;// 添加用戶public void addUser(User user) throws Exception;// 刪除用戶public void deleteUser(int id) throws Exception;// 修改用戶信息public void updateUser(User user) throws Exception; }?
4.2.2 ? ? dao接口實現類
package com.dzq.mybatis.dao.impl;import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory;import com.dzq.mybatis.dao.UserDao; import com.dzq.mybatis.domain.User;public class UserDaoImpl implements UserDao {// 需要向dao實現類中注入SqlSessionFactory// 這里通過構造方法注入private SqlSessionFactory sqlSessionFactory;public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {this.sqlSessionFactory = sqlSessionFactory;}@Overridepublic User findUserById(int id) throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();User user = sqlSession.selectOne("test.findUserById", id);// 釋放資源 sqlSession.close();return user;}@Overridepublic void addUser(User user) throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();// 執行插入sqlSession.insert("test.addUser", user);// 提交事務 sqlSession.commit();// 釋放資源 sqlSession.close();}@Overridepublic void deleteUser(int id) throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();sqlSession.delete("test.deleteUser", id);// 提交事務 sqlSession.commit();// 釋放資源 sqlSession.close();}@Overridepublic void updateUser(User user) throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();sqlSession.update("test.updateUser", user);// 提交事務 sqlSession.commit();// 釋放資源 sqlSession.close();}}4.2.4 ?測試代碼:
package com.dzq.mybatis.test;import java.io.InputStream;import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test;import com.dzq.mybatis.dao.UserDao; import com.dzq.mybatis.dao.impl.UserDaoImpl; import com.dzq.mybatis.domain.User;public class UserDaoImplTest {private SqlSessionFactory sqlSessionFactory;@Beforepublic void setUp() throws Exception {// 創建sqlSessionFactory// mybatis配置文件String resource = "SqlMapConfig.xml";// 得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);// 創建會話工廠,傳入mybatis的配置文件的信息sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);}@Testpublic void testFindUserById() throws Exception {// 創建UserDao對象UserDao userdao = new UserDaoImpl(sqlSessionFactory);// 調用userDao方法User user=userdao.findUserById(1);System.out.println(user.getUsername());}}?
4.2.5 ? ? 總結原始 dao開發問題
1、dao接口實現類方法中存在大量模板方法,設想能否將這些代碼提取出來,大大減輕程序員的工作量。
?
2、調用sqlsession方法時將statement的id硬編碼了
?
3、調用sqlsession方法時傳入的變量,由于sqlsession方法使用泛型,即使變量類型傳入錯誤,在編譯階段也不報錯,不利于程序員開發。
?
4.3 ? ?mapper代理方法(程序員只需要mapper接口(相當 于dao接口))
?
4.3.1???? 思路(mapper代理開發規范)
程序員還需要編寫mapper.xml映射文件
程序員編寫mapper接口需要遵循一些開發規范,mybatis可以自動生成mapper接口實現類代理對象。
?
開發規范:
1、在mapper.xml中namespace等于mapper接口地址
<!--命名空間,對sql進行分類管理,實現sql隔離 注意:使用mapper代理的方法開發,namespace就有特殊重要的作用 namespace等于mapper接口地址 --> <mapper namespace="com.dzq.mybatis.mapper.UserMapper">?
2、mapper.java接口中的方法名和mapper.xml中statement的id一致
3、mapper.java接口中的方法輸入參數類型和mapper.xml中statement的parameterType指定的類型一致。
4、mapper.java接口中的方法返回值類型和mapper.xml中statement的resultType指定的類型一致。
<select id="findUserById" parameterType="int" resultType="com.dzq.mybatis.domain.User">select * from user where id=#{id}</select> // 根據id查詢用戶信息public User findUserById(int id) throws Exception;總結:
以上開發規范主要是對下邊的代碼進行統一生成:
User user = sqlSession.selectOne("test.findUserById", id);
sqlSession.insert("test.insertUser", user);
。。。。
4.3.2 ? ??mapper.java
package com.dzq.mybatis.mapper;import java.util.List;import com.dzq.mybatis.domain.User;public interface UserMapper {public User findUserById(int id) throws Exception; }4.3.3?mapper.xml
<select id="findUserById" parameterType="int" resultType="com.dzq.mybatis.domain.User">select * from user where id=#{id}</select>4.3.4 ? ? 在SqlMapConfig.xml中加載mapper.xml
<!-- 加載 映射文件 --><mappers><mapper resource="sqlmap/User.xml"/><mapper resource="mapper/UserMapper.xml"/></mappers>4.3.5 ? ?測試
package com.dzq.mybatis.test;import java.io.InputStream;import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test;import com.dzq.mybatis.domain.User; import com.dzq.mybatis.mapper.UserMapper;public class UserMapperTest {private SqlSessionFactory sqlSessionFactory;@Beforepublic void setUp() throws Exception {// 創建sqlSessionFactory// mybatis配置文件String resource = "SqlMapConfig.xml";// 得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);// 創建會話工廠,傳入mybatis的配置文件的信息sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);}@Testpublic void testFindUserById() throws Exception {SqlSession sqlSession=sqlSessionFactory.openSession();//創建一個usermapper的對象UserMapper userMapper=sqlSession.getMapper(UserMapper.class);//調用userMapper方法User user=userMapper.findUserById(1);System.out.println(user.getUsername());}}4.3.6 ? ?整個接口
package com.dzq.mybatis.mapper;import java.util.List;import com.dzq.mybatis.domain.User;public interface UserMapper {// 根據id查詢用戶信息public User findUserById(int id) throws Exception;// 添加用戶public void addUser(User user) throws Exception;// 刪除用戶public void deleteUser(int id) throws Exception;// 根據用戶名稱查詢用戶列表public List<User> findUserByUserName(String username) throws Exception; }4.3.7 ? ? 一些問題總結
4.3.7.1????????????? 代理對象內部調用selectOne或selectList
如果mapper方法返回單個pojo對象(非集合對象),代理對象內部通過selectOne查詢數據庫。
如果mapper方法返回集合對象,代理對象內部通過selectList查詢數據庫。
4.3.7.2????????????? mapper接口方法參數只能有一個是否影響系統 開發
mapper接口方法參數只能有一個,系統是否不利于擴展維護。
系統 框架中,dao層的代碼是被業務層公用的。
即使mapper接口只有一個參數,可以使用包裝類型的pojo滿足不同的業務方法的需求。
?
注意:持久層方法的參數可以包裝類型、map。。。,service方法中建議不要使用包裝類型(不利于業務層的可擴展)。
?
5 ? ??SqlMapConfig.xml
mybatis的全局配置文件SqlMapConfig.xml,配置內容如下:
?
properties(屬性)
settings(全局配置參數)
typeAliases(類型別名)
typeHandlers(類型處理器)
objectFactory(對象工廠)
plugins(插件)
environments(環境集合屬性對象)
environment(環境子屬性對象)
transactionManager(事務管理)
dataSource(數據源)
mappers(映射器)
?
5.1???? properties屬性
需求:
將數據庫連接參數單獨配置在db.properties中,只需要在SqlMapConfig.xml中加載db.properties的屬性值。
在SqlMapConfig.xml中就不需要對數據庫連接參數硬編碼。
?
將數據庫連接參數只配置在db.properties中,原因:方便對參數進行統一管理,其它xml可以引用該db.properties。
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis jdbc.username=root jdbc.password= <!-- 加載屬性文件 --><properties resource="db.properties"><!--properties中還可以配置一些屬性名和屬性值 --><!-- <property name="jdbc.driver" value=""/> --></properties>properties特性:
?
注意: MyBatis 將按照下面的順序來加載屬性:
? ? ? 在 properties 元素體內定義的屬性首先被讀取。
? ? ? 然后會讀取properties 元素中resource或 url 加載的屬性,它會覆蓋已讀取的同名屬性。
? ? ? 最后讀取parameterType傳遞的屬性,它會覆蓋已讀取的同名屬性。
?
建議:
不要在properties元素體內添加任何屬性值,只將屬性值定義在properties文件中。
在properties文件中定義屬性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX
5.2 ? settings全局參數配置
?
mybatis框架在運行時可以調整一些運行參數。
比如:開啟二級緩存、開啟延遲加載。。
?
全局參數將會影響mybatis的運行行為。
?
5.3 ? ? typeAliases(別名)重點
?
5.3.1???? 需求
在mapper.xml中,定義很多的statement,statement需要parameterType指定輸入參數的類型、需要resultType指定輸出結果的映射類型。
?
如果在指定類型時輸入類型全路徑,不方便進行開發,可以針對parameterType或resultType指定的類型定義一些別名,在mapper.xml中通過別名定義,方便開發。
5.3.2 ? mybatis默認支持別名
5.3.3 ? ? 自定義別名
?
5.3.3.1????????????? 單個別名定義
<!-- 別名定義 --><typeAliases><!-- 針對單個別名的定義type:類型路徑alias:別名--><typeAlias type="com.dzq.mybatis.domain.User" alias="user"/></typeAliases>?
引用別名:
<select id="findUserById" parameterType="int" resultType="user">select * from user where id=#{id}</select>5.3.3.2??批量定義別名(常用)
<!-- 批量別名定義指定包名,mybatis自動掃描domain類,自動定義別名,別名就是類名--><package name="com.dzq.mybatis.domain"/>5.4 ? ?typeHandlers(類型處理器)
mybatis中通過typeHandlers完成jdbc類型和java類型的轉換。
?
通常情況下,mybatis提供的類型處理器滿足日常需要,不需要自定義.
?
mybatis支持類型處理器:
5.5 ?mappers(映射配置)
5.5.1???? 通過resource加載單個映射文件
<!-- 加載 映射文件 --><mappers><mapper resource="sqlmap/User.xml" /><mapper resource="mapper/UserMapper.xml" /></mappers>5.5.2 ? 通過mapper接口加載單個mapper
<!--通過mapper接口加載單個mapper 遵循一些規范:需要將mapper接口的類名和mapper.xml映射文件名稱保持一致,且在一個目錄上邊規范的前提是:你使用的是mapper代理的方法--><mapper class="com.dzq.mybatis.mapper.UserMapper"/>需要將mapper接口的類名和mapper.xml映射文件名稱保持一致,且在一個目錄
5.5.3 ?批量加載mapper(推薦使用)
<!--批量加載mapper(推薦使用) 指定mapper接口的包名,mybatis自動掃描包下所有mapper接口進行加載遵循一些規范:需要將mapper接口的類名和mapper.xml映射文件名稱保持一致,且在一個目錄上邊規范的前提是:你使用的是mapper代理的方法--><package name="com.dzq.mybatis.mapper"/>6 ? ?輸入映射
通過parameterType指定輸入參數的類型,類型可以是簡單類型、hashmap、pojo的包裝類型。
6.1???? 傳遞pojo的包裝對象
6.1.1???? 需求
完成用戶信息的綜合查詢,需要傳入查詢條件很復雜(可能包括用戶信息、其它信息,比如商品、訂單的)
6.1.2???? 定義包裝類型pojo
針對上邊需求,建議使用自定義的包裝類型的pojo。
在包裝類型的pojo中將復雜的查詢條件包裝進去。
package com.dzq.mybatis.domain;public class UserQueryVo { //這里包裝所需要的查詢條件//用戶查詢條件private UserCustom userCustom;//還可以包裝其他的查詢條件 商品、訂單public UserCustom getUserCustom() {return userCustom;}public void setUserCustom(UserCustom userCustom) {this.userCustom = userCustom;}}6.1.3 ? ?mapper.xml
在UserMapper.xml中定義用戶信息綜合查詢(查詢條件復雜,通過高級查詢進行復雜關聯查詢)。
<!-- 用戶信息的綜合查詢#{userCustom.sex}:取出包裝類型的性別信息${userCustom.username}:取出包裝類中的用戶名--><select id="findUserList" parameterType="com.dzq.mybatis.domain.UserQueryVo" resultType="com.dzq.mybatis.domain.UserCustom">select * from user where user.sex=#{userCustom.sex} and user.username like '%${userCustom.username}%'</select>6.1.4 ? ??mapper.java
//用戶信息綜合查詢public List<UserCustom> findUserList(UserQueryVo userQueryVo) throws Exception;?
6.1.5 ? ?測試代碼
@Testpublic void testFindUserList() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();// 創建一個usermapper的對象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//創建包裝對象,設置查詢條件UserQueryVo userQueryVo=new UserQueryVo();UserCustom userCustom=new UserCustom();userCustom.setSex("1");userCustom.setUsername("小明");userQueryVo.setUserCustom(userCustom);// 調用userMapper方法 List<UserCustom> list = userMapper.findUserList(userQueryVo);//sqlSession.close(); System.out.println(list);}?
7 ? ? 輸出映射
?
7.1???? resultType
使用resultType進行輸出映射,只有查詢出來的列名和pojo中的屬性名一致,該列才可以映射成功。
如果查詢出來的列名和pojo中的屬性名全部不一致,沒有創建pojo對象。
只要查詢出來的列名和pojo中的屬性有一個一致,就會創建pojo對象,不一致的屬性的值為null。
?
7.1.1???? 輸出簡單類型
7.1.1.1????????????? 需求
用戶信息的綜合查詢列表總數,通過查詢總數和上邊用戶綜合查詢列表才可以實現分頁。
?
7.1.1.2????????????? mapper.xml
<!-- 用戶信息綜合查詢總數parameterType:輸入類型和findUserList一致resultType:輸出結果類型為整型--><select id="findUserCount" parameterType="com.dzq.mybatis.domain.UserQueryVo" resultType="int">select count(*) from user where user.sex=#{userCustom.sex} and user.username like '%${userCustom.username}%'</select>?
7.1.1.3 ? ? ? ? ? ? ?mapper.java
//用戶信息綜合查詢總數public int findUserCount(UserQueryVo userQueryVo) throws Exception;?
7.1.1.4 ? ? ? ? ? ? 測試代碼
@Testpublic void testFindUserCount() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();// 創建一個usermapper的對象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//創建包裝對象,設置查詢條件UserQueryVo userQueryVo=new UserQueryVo();UserCustom userCustom=new UserCustom();userCustom.setSex("1");userCustom.setUsername("小明");userQueryVo.setUserCustom(userCustom);// 調用userMapper方法int count = userMapper.findUserCount(userQueryVo);System.out.println(count);}7.1.1.5 ? ? ? ? ? ? 小結
查詢出來的結果集只有一行且一列,可以使用簡單類型進行輸出映射。
7.1.2 ? 輸出pojo對象和pojo列表
?
不管是輸出的pojo單個對象還是一個列表(list中包括pojo),在mapper.xml中resultType指定的類型是一樣的。
在mapper.java指定的方法返回值類型不一樣:
???????? 1、輸出單個pojo對象,方法返回值是單個對象類型
? ? ? ? ??
// 根據id查詢用戶信息public User findUserById(int id) throws Exception;?
? ? ? ? ?2、輸出pojo對象list,方法返回值是List<Pojo>
? ? ? ? ?
// 根據用戶名稱查詢用戶列表public List<User> findUserByUserName(String username) throws Exception;?
生成的動態代理對象中是根據mapper方法的返回值類型確定是調用selectOne(返回單個對象調用)還是selectList (返回集合對象調用 ).
7.2 ? ? resultMap
mybatis中使用resultMap完成高級輸出結果映射。(一對一、一對多、多對多)(小入門)
7.2.1???? resultMap使用方法
如果查詢出來的列名和pojo的屬性名不一致,通過定義一個resultMap對列名和pojo屬性名之間作一個映射關系。
1、定義resultMap
2、使用resultMap作為statement的輸出映射類型
7.2.2???? 將下邊的sql使用User完成映射
SELECT id id_,username username_ FROM USER WHERE id=#{value}
User類中屬性名和上邊查詢列名不一致。
7.2.2.1????????????? 定義reusltMap
<!-- 定義resultMap將 select id id_,username username_ from user where id=#{id}查詢和User做一個映射type:resultMap最終映射的java對象類型,可以使用別名id:對resultMap的唯一標識--><resultMap type="user" id="userResultMap"><!-- id表示查詢結果集中唯一的標識 column:查詢出來的列名property:type中指定的pojo中的屬性名最終resultMap對column和property做一個映射關系(對應關系)--><id column="id_" property="id"/><!-- 對普通列的定義column:查詢出來的列名property:type中指定的pojo中的屬性名最終resultMap對column和property做一個映射關系(對應關系)--><result column="username_" property="username"/></resultMap>?
7.2.2.2 ? ? ??使用resultMap作為statement的輸出映射類型
<!-- 使用resultMap來進行輸出的映射 resultMap:指定定義的resultMap的id,如果resultMap在其他映射文件中,前邊需要加上namespace--><select id="findUserByIdResultMap" parameterType="int" resultMap="userResultMap">select id id_,username username_ from user where id=#{id}</select>7.2.2.3 ? ? ? ? ? ?mapper.java
//根據id查詢用戶信息,使用resultMap輸出public User findUserByIdResultMap(int id)throws Exception;7.2.2.4 ? ? ? ? ? ?測試
@Testpublic void testFindUserByIdResultMap() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();// 創建一個usermapper的對象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);// 調用userMapper方法User user = userMapper.findUserByIdResultMap(1);System.out.println(user.getUsername());}7.3 ? ?小結
使用resultType進行輸出映射,只有查詢出來的列名和pojo中的屬性名一致,該列才可以映射成功。
如果查詢出來的列名和pojo的屬性名不一致,通過定義一個resultMap對列名和pojo屬性名之間作一個映射關系。
8 ? ? ?動態sql
8.1???? 什么是動態sql
mybatis核心 對sql語句進行靈活操作,通過表達式進行判斷,對sql進行靈活拼接、組裝。
8.2???? 需求
用戶信息綜合查詢列表和用戶信息查詢列表總數這兩個statement的定義使用動態sql。
對查詢條件進行判斷,如果輸入參數不為空才進行查詢條件拼接。
8.3???? mapper.xml
<!-- 用戶信息的綜合查詢#{userCustom.sex}:取出包裝類型的性別信息${userCustom.username}:取出包裝類中的用戶名--><select id="findUserList" parameterType="com.dzq.mybatis.domain.UserQueryVo" resultType="com.dzq.mybatis.domain.UserCustom">select * from user <!-- where 可以自動的去掉條件中的第一個and --><where><if test="userCustom!=null"><if test="userCustom.sex!=null and userCustom.sex!=''">and user.sex=#{userCustom.sex} </if><if test="userCustom.username!=null and userCustom.username!=''">and user.username like '%${userCustom.username}%'</if></if></where></select><!-- 用戶信息綜合查詢總數parameterType:輸入類型和findUserList一致resultType:輸出結果類型為整型--><select id="findUserCount" parameterType="com.dzq.mybatis.domain.UserQueryVo" resultType="int">select count(*) from user <!-- where 可以自動的去掉條件中的第一個and --><where><if test="userCustom!=null"><if test="userCustom.sex!=null and userCustom.sex!=''">and user.sex=#{userCustom.sex} </if><if test="userCustom.username!=null and userCustom.username!=''">and user.username like '%${userCustom.username}%'</if></if></where></select>8.4 ? 測試代碼
public void testFindUserList() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();// 創建一個usermapper的對象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//創建包裝對象,設置查詢條件UserQueryVo userQueryVo=new UserQueryVo();UserCustom userCustom=new UserCustom();//由于使用了動態sql,如果不設置某個值,這個條件不會拼接到sql中//userCustom.setSex("1");userCustom.setUsername("小明");userQueryVo.setUserCustom(userCustom);// 調用userMapper方法 List<UserCustom> list = userMapper.findUserList(userQueryVo);//sqlSession.close(); System.out.println(list);}8.5 ? ?sql片段
?
8.5.1???? 需求
將上邊實現的動態sql判斷代碼塊抽取出來,組成一個sql片段。其它的statement中就可以引用sql片段。
方便程序員進行開發。
?
8.5.2???? 定義sql片段
!--定義sql片段id:sql片段的唯一標識經驗:基于單表定義sql片段,這樣可重用性才高sql片段中不要包括where--><sql id="query_user_where"><if test="userCustom!=null"><if test="userCustom.sex!=null and userCustom.sex!=''">and user.sex=#{userCustom.sex} </if><if test="userCustom.username!=null and userCustom.username!=''">and user.username like '%${userCustom.username}%'</if></if></sql>?
8.5.3 ? ?引用sql片段
在mapper.xml中定義的statement中引用sql片段:
<!-- 用戶信息的綜合查詢#{userCustom.sex}:取出包裝類型的性別信息${userCustom.username}:取出包裝類中的用戶名--><select id="findUserList" parameterType="com.dzq.mybatis.domain.UserQueryVo" resultType="com.dzq.mybatis.domain.UserCustom">select * from user <!-- where 可以自動的去掉條件中的第一個and --><where><!-- 這就是引用sql片段的id 如果refid不在本mapper中,需要加上namespace--><include refid="query_user_where"> </include><!-- 在這里還會引用其他sql片段,商品等 --></where></select> <!-- 用戶信息綜合查詢總數parameterType:輸入類型和findUserList一致resultType:輸出結果類型為整型--><select id="findUserCount" parameterType="com.dzq.mybatis.domain.UserQueryVo" resultType="int">select count(*) from user <!-- where 可以自動的去掉條件中的第一個and --><where><!-- 這就是引用sql片段的id 如果refid不在本mapper中,需要加上namespace--><include refid="query_user_where"> </include><!-- 在這里還會引用其他sql片段,商品等 --></where></select>?
8.6 ? ?foreach
?
向sql傳遞數組或List,mybatis使用foreach解析
?
8.6.1???? 需求
?
在用戶查詢列表和查詢總數的statement中增加多個id輸入查詢。
sql語句如下:
兩種方法:
SELECT * FROM USER WHERE id=1 OR id=10 OR id=16
SELECT * FROM USER WHERE id IN(1,10,16)
8.6.2 ? ?在輸入參數類型中添加List<Integer> ids傳入多個id
?
//傳入多個idprivate List<Integer> ids;public List<Integer> getIds() {return ids;}public void setIds(List<Integer> ids) {this.ids = ids;}?
8.6.3 ? ? 修改mapper.xml
WHERE id=1 OR id=10 OR id=16
在查詢條件中,查詢條件定義成一個sql片段,需要修改sql片段。
<if test="ids!=null"><!-- 使用foreach遍歷我們傳入的ids collection:指定輸入對象中集合屬性item:每次遍歷生成對象名open:開始遍歷時要拼接的串close:結束遍歷時拼接的串separator:遍歷的兩個對象中間所要拼接的串--><!-- 使用實現下邊的sql拼接and (id=1 or id=10 or id=16)--><foreach collection="ids" item="user_id" open="and (" close=")" separator="or"><!-- 每次遍歷所需要拼接的串-->id=#{user_id}</foreach></if>?
8.6.4 ? ? 測試代碼
@Testpublic void testFindUserList() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();// 創建一個usermapper的對象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//創建包裝對象,設置查詢條件UserQueryVo userQueryVo=new UserQueryVo();UserCustom userCustom=new UserCustom();//由于使用了動態sql,如果不設置某個值,這個條件不會拼接到sql中userCustom.setSex("1");userCustom.setUsername("小明");//傳入多個idList <Integer> ids=new ArrayList<Integer>();ids.add(1);ids.add(10);ids.add(16);userQueryVo.setUserCustom(userCustom);//將ids傳入statement中 userQueryVo.setIds(ids);// 調用userMapper方法 List<UserCustom> list = userMapper.findUserList(userQueryVo);//sqlSession.close();System.out.println(list.get(0).getUsername());}?
8.6.5 ?另外一個sql的實現:
?
<!--實現 and id in(1,10,16)拼接 --><foreach collection="ids" item="user_id" open=" and id in(" close=")" separator=","><!-- 每次遍歷所需要拼接的串-->#{user_id}</foreach>?
轉載于:https://www.cnblogs.com/xiaoduc-org/p/5517727.html
總結
以上是生活随笔為你收集整理的20160522--20160526----mybatis入门基础的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: zabbix 邮件报警
- 下一篇: 数据、事实、实体、值对象、事务、不变性