1 Mybatis概述
MyBatis 是支持定制化 SQL、存儲過程以及高級映射的優秀的持久層框架。MyBatis避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可以對配置和原生Map使用簡單的 XML 或注解,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。mybatis是一個優秀的基于java的持久層框架,它內部封裝了jdbc,使開發者只需要關注sql語句本身,而不需要花費精力去處理加載驅動、創建連接、創建statement等繁雜的過程。 mybatis通過xml或注解的方式將要執行的各種statement配置起來,并通過java對象和statement中sql的動態參數進行映射生成最終執行的sql語句,最后由mybatis框架執行sql并將結果映射為java對象并返回。 采用ORM思想解決了實體和數據庫映射的問題,對jdbc進行了封裝,屏蔽了jdbc api底層訪問細節,使我們不用與jdbc api打交道,就可以完成對數據庫的持久化操作。
mybatis技術曾經做過“更名”,早期這個團隊將技術框架稱為“iBatis",mybatis研發團隊”跳槽“google,從google跳”githup",將之前“ibatis"改為”mybaits"
mybatis中文網站:https://mybatis.net.cn/
2 mybatis框架技術定位
SSM框架與三層架構對應關系: Java提供JDBC,Spring提供SpringJdbc,mybatis
3 Mybatis的優勢:傳統jdbc存在的問題
JDBC操作步驟:
1、注冊驅動:Class.forName(“com.mysql.jdbc.Driver”)2、獲取連接:Connection connection=DriverManager.getConnection(url,username,password);3、獲取預處理對象:PreparedStatement pstm=connection.PreparedStatement(“sql”);4、執行sql操作:Result result=pstm.ExecuteQuery();/pstm.ExecuteReader().5、封裝結果集:While(result.next()){……}6、釋放資源:Result.close(); Pstm.close(); Connection.close();
分析:如果我們要在mysql中查詢一個內容應該怎樣操作才能得到結果?在mysql中我們只需要編寫出相應的sql語句即可,但是在jdbc中除了編寫sql語句外還要做很多額外的工作才能得到結果。能不能只編寫sql語句其余工作不再去關注呢?這就是mybatis的作用,我們唯一要做的就是思考怎樣去編寫高效的sql語句。
4 Mybatisの入門案例
創建maven控制臺程序,按照以下步驟完成mybatis開發環境搭建工作:
- pom.xml 導入mybatis依賴
<!-- mybatis依賴-->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.6</version>
</dependency>
<!-- 數據庫驅動-->
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.0.33</version>
</dependency>
- 項目中引入數據庫配置文件:db.properties
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/bookstore?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=false
jdbc.username=root
jdbc.password=root
- 添加mybatis核心配置文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--加載數據庫配置信息的文件:db.propertiesproperties:讀取classpath下面的配置文件,所以不用寫classpath--><properties resource="db.properties"/>
<!--
配置數據庫連接信息:從配置文件讀取對應的key的值
--><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments>
</configuration>
mybatis-confg.xml是mybatis的核心配置文件,該文件中要配置哪些東西呢:即基本的sql語句執行環境需要的就是數據驅動類型、數據庫服務器地址、數據庫服務器登錄賬號及密碼。
- 測試代碼
-
public class Tester {@Testpublic void test01()throws Exception{//1.讀取配置文件,構建mybatis核心執行對象String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//SqlSession可以理解為Connection,此行代碼的意思就是:讀取配置文件獲取數據庫連接的工廠對象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//通過工廠獲取數據庫連接對象SqlSession session = sqlSessionFactory.openSession();System.out.println(session);} }
5 Mybatis的CRUD操作
準備工作
確定要操作的數據庫和數據表后,按照持久層dao開發流程,完成代碼準備:
創建Notice實體類
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Notice {private Integer id;private String title;private String content;private Integer employeeId;//jdk1.8新增日期格式:LocalDateTime對象中包含:年月日時分秒組成private LocalDateTime createTime;
}
為表創建對應的dao接口:NoticeDao.java
/*** mybatis接口不再提供實現類,取而代之使用xml配置文件完成代碼執行,* xml文件主要作用:寫sql,成為sql映射文件* sql映射文件要求:文件名必須和接口同名,單詞一模一樣;文件存放位置和接口所在包一樣*/
public interface NoticeDao {/*** 查詢所有的帖子信息*/List<Notice> selectAll();
}
為NoticeDao接口創建SQL映射文件:NoticeDao.xml
mybatis中規定dao只是用來聲明方法的接口類,接口中方法的實現由mapper.xml映射文wrh來實現(sql語句)。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
mapper:sql映射文件.作用:配置sql語句namespace:命名空間,理解包 設置當前sql映射文件是對應哪個接口文件,填寫接口包名.類名
-->
<mapper namespace="com.woniu.dao.NoticeDao"><select id="" resultType=""></select>
</mapper>
注意:mapper.xml映射文件須和dao接口在同一包中且名稱相同。創建好的文件目錄如下圖所示:
案例一:查詢所有公告信息
修改NoticeDao.xml映射文件:為查詢方法配置對應的sql語句
注意:使用mapper代理開發時不用為dao接口編寫實現類,但是在mapper.xml文件中的mapper節點中的namespace必須指定與同名接口類的全限定名
<!--
id:指定sql為接口里面哪個方法提供的。id設置方法:方法名
resultType:方法返回值類型,如果方法返回值類型是集合,只需要填寫集合泛型類型即可指定類型時:必須使用全路徑:包名.類名
-->
<select id="selectAll" resultType="com.woniu.entity.Notice">SELECT id,title,content,employeeId,createtime from wy_notice
</select>
屬性說明:
id: 設置要實現的方法名稱resultType: 方法返回值類型,如果返回值是集合類型,寫集合的泛型類型。鑒于之前使用JdbcTemplate執行sql的經驗,我們可以這么理解resultType:resultType提供類型用于完成查詢結果與實體類映射關系,默認情況:entity定義屬性時屬性名和表列名單詞一樣的。
注意:使用mapper代理開發時mapper.xml中sql節點的id值必須與dao接口中的方法名稱一致
resultType屬性的值必須與dao接口中方法的返回值類型一致
核心配置文件中注冊Sql映射文件:NoticeDao.xml
每一個mapper.xml映射文件要能正確被程序解析到,還要在mybatis核心配置文件中進行注冊<mappers><mapper resource="com/woniu/dao/NoticeDao.xml"/>
</mappers>resource讀取的是classpath下的文件url地址,所以此處寫的是xml的路徑,不是接口的包名
常見異常:
如果沒有在核心配置文件中“注冊”sql映射文件,執行代碼時通常會提示以下異常:
測試代碼
@Test
public void test01()throws Exception{//1.讀取配置文件,構建mybatis核心執行對象String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//SqlSession可以理解為Connection,此行代碼的意思就是:讀取配置文件獲取數據庫連接的工廠對象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//通過工廠獲取數據庫連接對象SqlSession session = sqlSessionFactory.openSession();System.out.println(session);//執行sql語句了 重點,mapper其實就是依靠jdk代理為接口生成實現類NoticeDao noticeDaoImpl = sqlSession.getMapper(NoticeDao.class);//com.sun.proxy.$Proxy6 基于jdk代理,獲取接口對應的實現類System.out.println(noticeDaoImpl.getClass());List<Notice> notices = noticeDaoImpl.selectAll();notices.forEach(System.out::println);
}
觀察以上輸出結果,我們不難發現,mybatis的底層其實是利用JDK代理完成了dao接口對應實現類的動態生成。我們之前學過JDK代理的知識,我們可以這么理解
所以,在mybatis 框架中它將“通用且重復”的sql執行過程在程序運行過程中,動態增強到xml配置的sql語句前后。簡化了程序員持久層開發工作的負擔和繁復。
案例二:DML操作の添加公告信息
修改NoticeDao.xml映射文件:為insert方法配置對應的sql語句
注意:使用mapper代理開發時不用為dao接口編寫實現類,但是在mapper.xml文件中的mapper節點中的namespace必須指定與同名接口類的全限定名
<insert id="insert">insert wy_notice values(null,'測試數據','測試數據內容',1,now())
</insert>
屬性說明:
id: 設置要實現的方法名稱insert配置新增語句,該標簽一般只需要配置id即可因為insert沒有查詢結果集,所以insert標簽也就沒有resultType屬性了
測試代碼
@Test
public void testInsert()throws IOException{//1.獲取SqlSessionString resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession(true);//2.基于jdk代理,獲取要測試接口對應的實現類NoticeDao noticeDaoImpl = sqlSession.getMapper(NoticeDao.class);int insert = noticeDaoImpl.insert(null);System.out.println("insert語句執行后,受影響的行數:" + insert);
}
添加代碼執行成功后,數據庫數據并沒有新增的問題:
測試結果暗示的意思是數據添加成功,我們去刷新mysql數據庫,觀察發現并沒有出現”測試數據“這條新增數據,why???
真相是:mybatis在openSession()時,默認開啟了事務手動提交模式,所以在沒有代碼明確寫明“commit()"的情況下,程序操作的結果不會物理更新到數據表中。
解決方案如下:
方案一:手動提交事務
@Test
public void test01()throws Exception{//1.讀取配置文件,構建mybatis核心執行對象String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//SqlSession可以理解為Connection,此行代碼的意思就是:讀取配置文件獲取數據庫連接的工廠對象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//通過工廠獲取數據庫連接對象,SqlSession默認配置:事務手動提交,如果openSession(true)設置事務自動提交SqlSession session = sqlSessionFactory.openSession(); //sql執行:獲取UserDao接口的代理對象,NoticeDao noticeDaoImpl = session.getMapper(NoticeDao.class);int i = noticeDaoImpl.insert(null);System.out.println("新增方法執行結果是:" + i);//提交事務session.commit();//釋放資源session.close();
}
方案二:開啟事務自動提交模式
@Test
public void test01()throws Exception{//1.讀取配置文件,構建mybatis核心執行對象String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//SqlSession可以理解為Connection,此行代碼的意思就是:讀取配置文件獲取數據庫連接的工廠對象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//通過工廠獲取數據庫連接對象,SqlSession默認配置:事務手動提交,如果openSession(true)設置事務自動提交SqlSession session = sqlSessionFactory.openSession(true); //sql執行:獲取UserDao接口的代理對象,NoticeDao noticeDaoImpl = session.getMapper(NoticeDao.class);int i = noticeDaoImpl.insert(null);System.out.println("新增方法執行結果是:" + i);//釋放資源session.close();
}
擴展補充:SqlSession釋放資源的問題
SqlSession使用完畢后,也是要釋放數據庫資源的,所以此處如果想不寫“session.close()“,可以借助JDK1.8中try-catch的新語法,代碼如下所示:
@Testpublic void test01()throws Exception{//1.讀取配置文件,構建mybatis核心執行對象String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//SqlSession可以理解為Connection,此行代碼的意思就是:讀取配置文件獲取數據庫連接的工廠對象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//通過工廠獲取數據庫連接對象,SqlSession默認配置:事務手動提交,如果openSession(true)設置事務自動提交try(SqlSession session = sqlSessionFactory.openSession(true);) {//sql執行:獲取UserDao接口的代理對象,NoticeDao noticeDaoImpl = session.getMapper(NoticeDao.class);int i = noticeDaoImpl.insert(null);System.out.println("新增方法執行結果是:" + i);}}
6 日志框架 logback
模仿日志輸出,日志將程序執行過程,執行了什么sql,帶入什么參數,執行的是什么結果,執行出現問題具體描述信息…
作用:日志框架增強程序員跟蹤程序執行過程,對于發生一些程序問題,更好進行定位、分析。
日志框架選擇:Springboot內置日志框架:logback
logback使用步驟
-
pom.xml導入logback的依賴:整個日志框架需要導入三個依賴:logback-classic、logback-core、slf4j-api
-
<!-- 日志框架 --> <dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version> </dependency> <dependency><groupId>ch.qos.logback</groupId><artifactId>logback-core</artifactId><version>1.2.3</version> </dependency> <dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version> </dependency>
-
項目引入自己的日志配置文件:logback.xml
-
<configuration debug="false"><!-- appender配置輸出的位置和輸出日志格式CONSOLE:控制臺--><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%5p | %-40.40logger{39} :%m%n</pattern><charset>utf8</charset></encoder></appender><!-- 了解日志信息輸出的級別:TRACE < DEBUG < INFO < WARN < ERROR配置哪些日志在控制臺輸出,可以通過設置不同的日志級別控制日志級別的輸出原則:設置級別時,會輸出當前設置級別的日志以及比當前更高級別的日志--><!-- logger:特定設置,屬性 name:特定配置針對的包名,酌情修改 level:DEBUG additivity:覆蓋默認配置 --><logger name="com.woniu" level="DEBUG" additivity="false"><appender-ref ref="CONSOLE"/></logger> <!-- root:基礎配置,level:info --><root level="INFO"><appender-ref ref="CONSOLE" /></root> </configuration>
-
自己使用log對象輸出日志信息
-
@Slf4j //引入日志管理器 public class xxx{@Testpublic void test00(){String str="helloworld";//觀察控制臺能否輸出對應的信息:信息是否可以輸出與logback.xml配置的級別有關系log.trace("trace str{}",str);log.debug("debug str{}",str);log.info("info str{}",str);log.warn("warn str{}",str);log.error("error str{}",str);} }
按照步驟配置好日志框架后,重新啟動上面案例的測試代碼,就可以在控制臺看到sql的輸出信息了。
7 mybatis中#{}和${}設置sql參數對比
案例:根據id查詢用戶信息
修改UserDao.xml中配置:使用${}帶入參數
<select id="selectById" resultType="com.woniu.entity.Users">select * from wy_employee where id=${id}</select>
執行測試方法
@Test
public void test01()throws Exception{//1.讀取配置文件,構建mybatis核心執行對象String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//SqlSession可以理解為Connection,此行代碼的意思就是:讀取配置文件獲取數據庫連接的工廠對象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//通過工廠獲取數據庫連接對象,SqlSession默認配置:事務手動提交,如果openSession(true)設置事務自動提交try(SqlSession session = sqlSessionFactory.openSession(true);) {System.out.println(session);//sql執行:獲取UserDao接口的代理對象,UsersDao usersDaoImpl = session.getMapper(UsersDao.class);System.out.println(usersDaoImpl.getClass());//調用方法,獲取執行結果List<Users> list = usersDaoImpl.selectAll("id");list.forEach(System.out::println);}
}
修改UserDao.xml中配置:使用#{}帶入參數
<select id="selectById" resultType="com.woniu.entity.Users">select * from wy_employee where id=#{id}</select>
再次執行同一個測試方法,觀察兩次測試的日志輸出結果:
由上圖得出以下結論:
#{}與${}的區別:#{}:使用?占位符,即是將sql語句編譯好后再取值,能夠有效防止sql注入,#{}取的是屬性中的值${}:是sql后面直接拼接參數值,即取值后再編譯語句,不能防止注入。
適用場景:${}方式一般用于傳入數據庫對象,例如傳入表名或者列名,就只能使用${}帶入參數。在實際使用中能使用#{}就不用${},
案例2:根據指定列名完成查詢結果的排序
-
UsersDao.java中定義方法
List<Users> selectAll(String name);
-
UsersDao.xml配置sql
select * from wy_employee order by ${name} desc -
測試代碼
@Test
public void test01()throws Exception{//1.讀取配置文件,構建mybatis核心執行對象String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//SqlSession可以理解為Connection,此行代碼的意思就是:讀取配置文件獲取數據庫連接的工廠對象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//通過工廠獲取數據庫連接對象,SqlSession默認配置:事務手動提交,如果openSession(true)設置事務自動提交try(SqlSession session = sqlSessionFactory.openSession(true);) {System.out.println(session);//sql執行:獲取UserDao接口的代理對象,UsersDao usersDaoImpl = session.getMapper(UsersDao.class);System.out.println(usersDaoImpl.getClass());//調用方法,獲取執行結果List<Users> list = usersDaoImpl.selectAll("id");list.forEach(System.out::println);}
}
- 將UserDao.xml中sql修改成#{}再次執行單元測試
<select id="selectAll" resultType="com.woniu.entity.Users">select * from wy_employee order by #{name} desc
</select>
觀察執行結果可以發現:#{}帶入參數時,查詢結果沒有排序。只有使用${}帶入參數時,才有排序的效果
8 #{}帶入參數的寫法
#{}帶入參數時,使用什么名稱來引用值,分為三種場景區別:
1 方法有且只有一個參數,并參數是基本類型或String,#{隨便寫}
形如:Users selectById(Long id);
配置sql:select * from wy_employee where id=#{隨便寫,一般見詞知意形參名}2 方法有且只有一個參數,并參數是對象,#{對象的屬性名}
形如:insert(user u) update(User u)3 方法N個參數 #{參數} 借助注解@Param給參數設置引用名
形如:
int update(@Param("pkId") Long id, @Param("pwd") String password,@Param("username") String realName);
xml文件:update wy_employee set password=#{pwd},realname=#{username} where id=#{pkId}
案例:新增公告信息
- NoticeDao.java定義新增方法
-
int insert(Notice notice);
- NoticeDao.xml配置insert語句
-
<insert id="insert">insert wy_notice values(null,#{title},#{content},#{userId},#{createTime}) </insert>
- MyTester.java測試代碼
-
@Test public void test02()throws Exception{//1 指定mybatis開發環境基于哪個配置文件來使用resource基于classpathString resource = "mybatis-config.xml";//2 基于配置文件構建IO輸入流InputStream inputStream = Resources.getResourceAsStream(resource);//SqlSession本質就是Connection SqlSessionFactory是連接池SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//執行sql語句,驗證執行結果,連接池獲取連接對象時,其實需要指定事務提交方式,如果不指定默認不是自動提交模式try (SqlSession session = sqlSessionFactory.openSession(true)) {//獲取dao實現類對象,mybatis依據JDK代理模式動態獲取實現類型NoticeDao implProxy = session.getMapper(NoticeDao.class);int i = implProxy.insert(Notice.builder().title("aaaaaaaaaaaa").content("bbbbbbbbbbbbbbbbbbbbbbb").userId(1).createTime(LocalDateTime.now()).build());System.out.println("本次新增的數據行數:"+i);}
- 跟蹤控制臺輸出的日志結果
常見坑點
sql映射文件沒有在核心配置文件中注冊時:
Sql映射文件和所實現的接口文件不在同一個目錄
如何確認接口文件和sql映射文件是否在一個目錄呢?可以通過mvn:compile編譯項目后,觀察target中的classes目錄。同一目錄的現象如下所示:
UsersDao.java和UsersDao.xml“緊緊挨在一起“
i