一、MyBatis簡介
1.1 框架概念
框架,就是軟件的半成品,完成了軟件開發過程中的通用操作,程序員只需很少或者不用進行加工就能夠實現特定的功能,從而簡化開發人員在軟件開發中的步驟,提高開發效率。
1.2 常用框架
- MVC框架:簡化了Servlet的開發步驟
- Struts
- Struts2
SpringMVC
- 持久層框架:完成數據庫操作的框架
- apache DBUtils
- Hibernate
- Spring JPA
MyBatis
- EJB3.0
- 膠水框架:
Spring
SSM Spring SpringMVC MyBatis
SSH Spring Struts2 Hibernate
1.3 MyBatis介紹
MyBatis是一個
半自動
的ORM
框架ORM(Object Relational Mapping)對象關系映射,將Java中的一個對象與數據表中一行記錄一一對應。
ORM框架提供了實體類與數據表的映射關系,通過映射文件的配置,實現對象的持久化。
- MyBatis的前身是iBatis,iBatis是Apache軟件基金會提供的一個開源項目
- 2010年iBatis遷移到Google code,正式更名為MyBatis
- 2013年遷移到Github托管
- MyBatis特點:
- 支持自定義SQL、存儲過程
- 對原有的JDBC進行了封裝,幾乎消除了所有JDBC代碼,讓開發者只需關注SQL本身
- 支持XML和注解配置方式自定完成ORM操作,實現結果映射
二、MyBatis框架部署
框架部署,就是將框架引入到我們的項目中
2.1 創建Maven項目
- Java工程
- Web工程
2.2 在項目中添加MyBatis依賴
-
在pom.xml中添加依賴
- mybatis
- mysql driver
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version> </dependency><!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.6</version> </dependency>
2.3 創建MyBatis配置文件
-
創建自定義模板:選擇resources----右鍵New----Edit File Templates
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-P4JExyWD-1639493777615)(imgs/1616144597211.png)]
-
在resources中創建名為
mybatis-config.xml
的文件 -
在
mybatis-config.xml
文件配置數據庫連接信息<?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><!-- 在environments配置數據庫連接信息 --><!-- 在environments標簽中可以定義多個environment標簽,每個environment標簽可以定義一套連接配置 --><!-- default屬性,用來指定使用哪個environment標簽 --><environments default="mysql"><environment id="mysql"><!--transactionManager標簽用于配置數據庫管理方式--><transactionManager type="JDBC"></transactionManager><!--dataSource標簽就是用來配置數據庫連接信息 --><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/db_2010_fmwy?characterEncoding=utf-8"/><property name="username" value="root"/><property name="password" value="admin123"/></dataSource></environment></environments></configuration>
三、MyBatis框架使用
案例:學生信息的數據庫操作
3.1 創建數據表
tb_students |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FppE0G06-1639493777619)(imgs/1616145569298.png)] |
3.2 創建實體類
Student.java |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GSFZ4RV3-1639493777619)(imgs/1616145876310.png)] |
3.3 創建DAO接口,定義操作方法
StudentDAO.java |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-H2R7Xmra-1639493777621)(imgs/1616147015300.png)] |
3.4 創建DAO接口的映射文件
- 在
resources
目錄下,新建名為mappers
文件夾 - 在
mappers
中新建名為StudentMapper.xml
的映射文件(根據模板創建) - 在映射文件中對DAO中定義的方法進行實現:
<?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文件相當于DAO接口的‘實現類’,namespace屬性要指定`實現`DAO接口的全限定名-->
<mapper namespace="com.qfedu.dao.StudentDAO"><insert id="insertStudent">insert into tb_students(stu_num,stu_name,stu_gender,stu_age)values(#{stuNum},#{stuName},#{stuGender},#{stuAge})</insert><delete id="deleteStudent">delete from tb_students where stu_num=#{stuNum}</delete></mapper>
3.5 將映射文件添加到主配置文件
mybatis-config.xml |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Q0lf9JK0-1639493777621)(imgs/1616146926729.png)] |
四、單元測試
4.1 添加單元測依賴
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version>
</dependency>
4.2 創建單元測試類
在被測試類名后alt+insert — 選擇Test |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-VMGaSWhm-1639493777622)(imgs/1616147095936.png)] |
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2uvbBYCd-1639493777623)(imgs/1616147180562.png)] |
4.3 測試代碼
package com.qfedu.dao;import com.qfedu.pojo.Student;
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 java.io.IOException;
import java.io.InputStream;import static org.junit.Assert.*;public class StudentDAOTest {@org.junit.Testpublic void insertStudent() {try {//加載mybatis配置文件InputStream is = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();//會話工廠SqlSessionFactory factory = builder.build(is);//會話(連接)SqlSession sqlSession = factory.openSession();//通過會話獲取DAO對象StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);//測試StudentDAO中的方法int i = studentDAO.insertStudent(new Student(0, "10001", "張三", "男", 21));//需要手動提交sqlSession.commit();System.out.println(i);} catch (IOException e) {e.printStackTrace();}}@org.junit.Testpublic void deleteStudent() {}
}
五、MyBatis的CRUD操作
案例:學生信息的增刪查改
5.1 添加操作
略
5.2 刪除操作
根據學號刪除一條學生信息
- 在StudentDAO中定義刪除方法
StudentDAO |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HCObnCRl-1639493777624)(imgs/1616463209812.png)] |
- 在StudentMapper.xml中對接口方法進行“實現”
StudentMapper.xml |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AbKpsHuZ-1639493777625)(imgs/1616463282187.png)] |
- 測試:在StudentDAO的測試類中添加測試方法
StudentDAOTest |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-hUYCWgfM-1639493777626)(imgs/1616464771784.png)] |
5.3 修改操作
根據學生學號,修改其他字段信息
- 在StudentDAO接口中定義修改方法
StudentDAO |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-McpeRIEg-1639493777626)(imgs/1616465607290.png)] |
- 在StudentMapper.xml中“實現”接口中定義的修改方法
StudentMapper.xml |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-14p5zRZL-1639493777627)(imgs/1616465639080.png)] |
- 單元測試
StudentDAOTest |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xYIcjRfO-1639493777628)(imgs/1616465667657.png)] |
5.4 查詢操作-查詢所有
- 在StudentDAO接口定義操作方法
StudentDAO |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-1oDFXZF6-1639493777629)(imgs/1616467216382.png)] |
- 在StudentMapper.xml中“實現”DAO中定義的方法
StudentMapper.xml |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gGwmF4HX-1639493777629)(imgs/1616467314082.png)] |
- 單元測試
StudentDAOTest |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xci0oBM2-1639493777630)(imgs/1616467336432.png)] |
5.5 查詢操作-查詢一條記錄
根據學號查詢一個學生信息
- 在StudentDAO接口中定義方法
StudentDAO |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZfqOcjhG-1639493777631)(imgs/1616469113754.png)] |
- 在StudentDAOMapper.xml中配置StudentDAO接口的方法實現——SQL
StudentDAOMapper.xml |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fwMetDD6-1639493777631)(imgs/1616469142604.png)] |
- 單元測試
StudentDAOTest |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-VBclfidK-1639493777638)(imgs/1616469154258.png)] |
5.6 查詢操作-多參數查詢
分頁查詢(參數 start , pageSize)
- 在StudentDAO中定義操作方法,如果方法有多個參數,使用
@Param
注解聲明參數的別名
StudentDAO |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-yIXlmzgc-1639493777638)(imgs/1616470534343.png)] |
- 在StudentMapper.xml配置sql時,使用
#{別名}
獲取到指定的參數
StudentMapper.xml |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HI4IVPTg-1639493777639)(imgs/1616470612608.png)] |
注意
如果DAO操作方法沒有通過@Param指定參數別名,在SQL中也可以通過arg0,arg1...
或者param1,param2,...
獲取參數
5.7 查詢操作-查詢總記錄數
- 在StudentDAO接口中定義操作方法
StudentDAO |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JWtfTDQ5-1639493777639)(imgs/1616471133486.png)] |
- 在StudentMapper.xml配置sql,通過resultType指定當前操作的返回類型為int
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GIQtkvSD-1639493777640)(imgs/1616471056064.png)] |
5.8 添加操作回填生成的主鍵
- StduentMapper.xml的添加操作標簽——
insert
<!-- useGeneratedKeys 設置添加操作是否需要回填生成的主鍵 -->
<!-- keyProperty 設置回填的主鍵值賦值到參數對象的哪個屬性 -->
<insert id="insertStudent" useGeneratedKeys="true" keyProperty="stuId">insert into tb_students(stu_num, stu_name, stu_gender, stu_age)values (#{stuNum}, #{stuName}, #{stuGender}, #{stuAge})
</insert>
六、MyBatis工具類封裝
- MyBatisUtil
public class MyBatisUtil {private static SqlSessionFactory factory;private static final ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>();static{try {InputStream is = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();factory = builder.build(is);} catch (IOException e) {e.printStackTrace();}}public static SqlSessionFactory getFactory(){return factory;}public static SqlSession getSqlSession(){SqlSession sqlSession = local.get();if(sqlSession == null ){sqlSession = factory.openSession();local.set(sqlSession);}return sqlSession;}public static <T extends Object> T getMapper(Class<T> c){SqlSession sqlSession = getSqlSession();return sqlSession.getMapper(c);}}
七、事務管理
SqlSession 對象
- getMapper(DAO.class) : 獲取Mapper(DAO接口的實例)
- 事務管理
7.1 手動提交事務
sqlSession.commit();
提交事務sqlSession.rollback();
事務回滾
測試類中進行事務管理
@Test
public void insertStudent() {SqlSession sqlSession = MyBatisUtil.getSqlSession();//1.當我們獲取sqlSession對象時,就默認開啟了事務try{//通過會話獲取DAO對象StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);//測試StudentDAO中的方法Student student = new Student(0, "10005", "Lily", "女", 21);int i = studentDAO.insertStudent(student);//2.操作完成并成功之后,需要手動提交sqlSession.commit();}catch (Exception e){//3.當操作出現異常,調用rollback進行回滾sqlSession.rollback();}
}
業務邏輯層手動事務管理
public class StudentServiceImpl implements StudentService {public boolean addStudent(Student student) {boolean b = false;SqlSession sqlSession = MyBatisUtil.getSqlSession();try{StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);int i = studentDAO.insertStudent(student);b = i>0;sqlSession.commit();}catch (Exception e){sqlSession.rollback();}return b;}
}
7.2 自動提交事務
通過SqlSessionFactory調用openSession方法獲取SqlSession對象時,可以通過參數設置事務是否自動提交:
如果參數設置為true,表示自定提交事務: factory.openSession(true);
如果參數設置為false,或者不設置參數,表示手動提交:factory.openSession();/factory.openSession(false);
MyBatisUtil優化
public class MyBatisUtil {private static SqlSessionFactory factory;private static final ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>();static{try {InputStream is = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();factory = builder.build(is);} catch (IOException e) {e.printStackTrace();}}public static SqlSessionFactory getFactory(){return factory;}private static SqlSession getSqlSession(boolean isAutoCommit){SqlSession sqlSession = local.get();if(sqlSession == null ){sqlSession = factory.openSession(isAutoCommit);local.set(sqlSession);}return sqlSession;}//手動事務管理public static SqlSession getSqlSession(){return getSqlSession(false);}//自動事務提交public static <T extends Object>T getMapper(Class<T> c){SqlSession sqlSession = getSqlSession(true);return sqlSession.getMapper(c);}}
測試操作
@Test
public void testDeleteStudent() {StudentDAO studentDAO = MyBatisUtil.getMapper(StudentDAO.class);int i = studentDAO.deleteStudent("10001");
}
業務邏輯層自動事務管理
public class StudentServiceImpl implements StudentService {private StudentDAO studentDAO = MyBatisUtil.getMapper(StudentDAO.class);public boolean addStudent(Student student) {int i = studentDAO.insertStudent(student);boolean b = i>0;return b;}
}
八、MyBatis主配置文件
mybatis-config.xml 是MyBatis框架的主配置文件,只要用于配置MyBatis數據源及屬性信息
8.1 properties標簽
用于設置鍵值對,或者加載屬性文件
- 在resources目錄下創建
jdbc.properties
文件,配置鍵值對如下:
mysql_driver=com.mysql.jdbc.Driver
mysql_url=jdbc:mysql://localhost:3306/db_2010_fmwy?characterEncoding=utf-8
mysql_username=root
mysql_password=admin123
- 在mybatis-config.xml中通過
properties
標簽引用jdbc.properties
文件;引入之后,在配置environment時可以直接使用jdbc.properties的key獲取對應的value
mybatis-config.xml |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-DqVD8IaY-1639493777640)(imgs/1616482928813.png)] |
8.2 settings標簽
<!--設置mybatis的屬性-->
<settings><!-- 啟動二級緩存--><setting name="cacheEnabled" value="true"/><!-- 啟動延遲加載 --><setting name="lazyLoadingEnabled" value="true"/>
</settings>
8.3 typeAliases標簽
<!--typeAliases標簽用于給實體類取別名,在映射文件中可以直接使用別名來替代實體類的全限定名-->
<typeAliases><typeAlias type="com.qfedu.pojo.Student" alias="Student"></typeAlias><typeAlias type="com.qfedu.pojo.Book" alias="Book"></typeAlias>
</typeAliases>
8.4 plugins標簽
<!--plugins標簽,用于配置MyBatis插件(分頁插件)-->
<plugins><plugin interceptor=""></plugin>
</plugins>
8.5 environments標簽
<!-- 在environments配置數據庫連接信息 -->
<!-- 在environments標簽中可以定義多個environment標簽,每個environment標簽可以定義一套連接配置 -->
<!-- default屬性,用來指定使用哪個environment標簽 -->
<environments default="mysql"><!-- environment 標簽用于配置數據庫連接信息 --><environment id="mysql"><!--transactionManager標簽用于配置數據庫管理方式type="JDBC" 可以進行事務的提交和回滾操作type="MANAGED" 依賴容器完成事務管理,本身不進行事務的提交和回滾操作 --><transactionManager type="JDBC"></transactionManager><!--dataSource標簽就是用來配置數據庫連接信息 POOLED|UNPOOLED --><dataSource type="POOLED"><property name="driver" value="${mysql_driver}"/><property name="url" value="${mysql_url}"/><property name="username" value="${mysql_username}"/><property name="password" value="${mysql_password}"/></dataSource></environment></environments>
8.6 mappers標簽
加載映射配置(映射文件、DAO注解)
<!--mappers標簽用于載入映射文件-->
<mappers><mapper resource="mappers/StudentMapper.xml"></mapper>
</mappers>
九、映射文件
9.1 MyBatis Mapper初始化
XML文件解析:讀取xml文件中的標簽配置封裝到Java對象中
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-eFmhNoJc-1639493777641)(imgs/1616487875112.png)]
9.2 mapper根標簽
mapper文件相當于DAO接口的‘實現類’,namespace屬性要指定
實現
DAO接口的全限定名
9.3 insert標簽
聲明添加操作(sql: insert …)
常用屬性
id屬性,綁定對應DAO接口中的方法
parameterType屬性,用以指定接口中對應方法的參數類型(可省略)
useGeneratedKeys屬性, 設置添加操作是否需要回填生成的主鍵
keyProperty屬性,指定回填的id設置到參數對象中的哪個屬性
timeout屬性,設置此操作的超時時間,如果不設置則一直等待
主鍵回填
<insert id="insertStudent" useGeneratedKeys="true" keyProperty="stuId">insert into tb_students(stu_num, stu_name, stu_gender, stu_age)values (#{stuNum}, #{stuName}, #{stuGender}, #{stuAge})
</insert>
<insert id="insertStudent" ><selectKey keyProperty="stuId" resultType="java.lang.Integer">select last_insert_id()</selectKey>insert into tb_students(stu_num, stu_name, stu_gender, stu_age)values (#{stuNum}, #{stuName}, #{stuGender}, #{stuAge})
</insert>
9.4 delete標簽
聲明刪除操作
9.5 update標簽
聲明修改操作
9.6 select標簽
聲明查詢操作
id屬性, 指定綁定方法的方法名
parameterType屬性,設置參數類型
resultType屬性,指定當前sql返回數據封裝的對象類型(實體類)
resultMap屬性,指定從數據表到實體類的字段和屬性的對應關系
useCache屬性,指定此查詢操作是否需要緩存
timeout屬性,設置超時時間
9.7 resultMap標簽
<!-- resultMap標簽用于定義實體類與數據表的映射關系(ORM) -->
<resultMap id="studentMap" type="Student"><id column="sid" property="stuId"/><result column="stu_num" property="stuNum"/><result column="stu_name" property="stuName"/><result column="stu_gender" property="stuGender"/><result column="stu_age" property="stuAge"/>
</resultMap>
9.8 cache標簽
設置當前DAO進行數據庫操作時的緩存屬性設置
<cache type="" size="" readOnly="false"/>
9.9 sql和include
SQL片段
<sql id="wanglaoji">sid , stu_num , stu_name , stu_gender , stu_age</sql><select id="listStudents" resultMap="studentMap">select <include refid="wanglaoji"/> from tb_students
</select>
十、分頁插件
分頁插件是一個獨立于MyBatis框架之外的第三方插件;
10.1 添加分頁插件的依賴
PageHelper
<!-- pagehelper分頁插件 -->
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.1.10</version>
</dependency>
10.2 配置插件
在mybatis的主配置文件
mybatis-config.xml
中通過plugins
標簽進行配置
<!--plugins標簽,用于配置MyBatis插件(分頁插件)-->
<plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
10.3 分頁實例
對學生信息進行分頁查詢
@Test
public void testListStudentsByPage() {StudentDAO studentDAO = MyBatisUtil.getMapper(StudentDAO.class); //sqlSessionPageHelper.startPage(2,4);List<Student> students = studentDAO.listStudents();PageInfo<Student> pageInfo = new PageInfo<Student>(students);//pageInfo中就包含了數據及分頁信息
}
帶條件分頁
@Test
public void testListStudentsByPage() {StudentDAO studentDAO = MyBatisUtil.getMapper(StudentDAO.class); //sqlSessionPageHelper.startPage(2,4);//List<Student> students = studentDAO.listStudents();List<Student> list = studentDAO.listStudentsByGender("女");PageInfo<Student> pageInfo = new PageInfo<Student>(list);//pageInfo中就包含了數據及分頁信息
}
十一、關聯映射
11.1 實體關系
實體——數據實體,實體關系指的就是數據與數據之間的關系
例如:用戶和角色、房屋和樓棟、訂單和商品
實體關系分為以下四種:
一對一關聯
實例:人和身份證、學生和學生證、用戶基本信息和詳情
數據表關系:
-
主鍵關聯(用戶表主鍵 和詳情主鍵相同時,表示是匹配的數據)
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lF3JppTg-1639493777641)(imgs/1616550990633.png)]
-
唯一外鍵關聯
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9kQgWLt5-1639493777642)(imgs/1616551159843.png)]
一對多關聯、多對一關聯
實例:
- 一對多: 班級和學生 、 類別和商品、樓棟和房屋
- 多對一:學生和班級 、 商品和類別
數據表關系:
- 在多的一端添加外鍵和一的一段進行關聯
多對多關聯
實例:用戶和角色、角色和權限、房屋和業主、學生和社團、訂單和商品
數據表關系:建立第三張關系表添加兩個外鍵分別與兩張表主鍵進行關聯
用戶(user_id) 用戶角色表(uid,rid) 角色(role_id)
11.2 創建項目,部署MyBatis框架
-
創建web項目(maven)
<!-- 添加web依賴 --> <dependency><groupId>javax.servlet</groupId><artifactId>jsp-api</artifactId><version>2.0</version><scope>provided</scope> </dependency> <dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope> </dependency>
-
部署MyBatis框架
- 添加依賴
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.6</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version> </dependency>
- 配置文件
- 幫助類
public class MyBatisUtil {private static SqlSessionFactory factory;private static final ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>();static{try {InputStream is = Resources.getResourceAsStream("mybatis-config.xml");factory = new SqlSessionFactoryBuilder().build(is);} catch (IOException e) {e.printStackTrace();}}public static SqlSessionFactory getSqlSessionFactory(){return factory;}public static SqlSession getSqlSession(boolean isAutoCommit){SqlSession sqlSession = local.get();if(sqlSession == null){sqlSession = factory.openSession(isAutoCommit);local.set(sqlSession);}return sqlSession;}public static SqlSession getSqlSession(){return getSqlSession(false);}public static <T extends Object>T getMapper(Class<T> c){SqlSession sqlSession = getSqlSession(true);return sqlSession.getMapper(c);}}
11.3 一對一關聯
實例:用戶—詳情
11.3.1 創建數據表
-- 用戶信息表
create table users(user_id int primary key auto_increment,user_name varchar(20) not null unique,user_pwd varchar(20) not null,user_realname varchar(20) not null,user_img varchar(100) not null
);-- 用戶詳情表
create table details(detail_id int primary key auto_increment,user_addr varchar(50) not null,user_tel char(11) not null,user_desc varchar(200),uid int not null unique-- constraint FK_USER foreign key(uid) references users(user_id)
);
11.3.2 創建實體類
-
User
@Data @NoArgsConstructor @AllArgsConstructor @ToString public class User {private int userId;private String userName;private String userPwd;private String userRealname;private String userImg; }
-
Detail
@Data @NoArgsConstructor @AllArgsConstructor @ToString public class Detail {private int detailId;private String userAddr;private String userTel;private String userDesc;private int userId; }
11.3.3 添加操作(事務)
測試代碼 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-EQ0GrcR5-1639493777642)(imgs/1616558872585.png)] |
11.3.4 一對一關聯查詢
在查詢用戶的同時關聯查詢出與之對應的詳情
實體
User | Detail |
---|---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7O8YTeN4-1639493777643)(imgs/1616557527886.png)] | [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4guaBShg-1639493777644)(imgs/1616557509389.png)] |
映射文件
連接查詢 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-VLVqCWzx-1639493777644)(imgs/1616558061073.png)] |
子查詢 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-rp27lso2-1639493777645)(imgs/1616558696902.png)] |
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JcX1ZueX-1639493777645)(imgs/1616558739190.png)] |
11.4 一對多關聯
案例:班級(1)—學生(n)
11.4.1 創建數據表
-- 創建班級信息表
create table classes(cid int primary key auto_increment,cname varchar(30) not null unique,cdesc varchar(100)
);-- 創建學生信息表
create table students(sid char(5) primary key,sname varchar(20) not null,sage int not null,scid int not null
);
11.4.2 創建實體類
Clazz | Student |
---|---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9UKos2PN-1639493777646)(imgs/1616566868757.png)] | [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bubopJKg-1639493777646)(imgs/1616566709567.png)] |
11.4.3 關聯查詢
當查詢一個班級的時候, 要關聯查詢出這個班級下的所有學生
連接查詢
連接查詢映射配置 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-IX7Z9twQ-1639493777647)(imgs/1616567949549.png)] |
子查詢
子查詢映射配置 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-rXvcuBMD-1639493777648)(imgs/1616568410749.png)] |
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-TNAheVr2-1639493777648)(imgs/1616568443022.png)] |
11.5 多對一關聯
實例:學生(n)—班級(1)
當查詢一個學生的時候,關聯查詢這個學生所在的班級信息
11.5.1 創建實體類
Student | Clazz |
---|---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-pHLhsn1S-1639493777649)(imgs/1616568763050.png)] | [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CWjmY7v3-1639493777649)(imgs/1616568809974.png)] |
11.5.2 關聯查詢
連接查詢
連接查詢映射配置 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bLeWPLcu-1639493777650)(imgs/1616569488794.png)] |
子查詢
子查詢映射配置 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ro5YETEW-1639493777650)(imgs/1616569897430.png)] |
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AdruQhxF-1639493777651)(imgs/1616569932235.png)] |
11.6 多對多關聯
案例:學生(m)—課程(n)
11.6.1 創建數據表
-- 學生信息表(如上)
-- 課程信息表
create table courses(course_id int primary key auto_increment,course_name varchar(50) not null
);
-- 選課信息表/成績表(學號、課程號、成績)
create table grades(sid char(5) not null,cid int not null,score int not null
);
11.6.2 關聯查詢
查詢學生時,同時查詢學生選擇的課程
Student | Course |
---|---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ziu9JqnZ-1639493777651)(imgs/1616577612042.png)] | [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jAn7ebCi-1639493777652)(imgs/1616577631853.png)] |
根據課程編號查詢課程時,同時查詢選擇了這門課程的學生
Student | Course |
---|---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-DIVBZMjd-1639493777652)(imgs/1616577675018.png)] | [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9Y5IQBNu-1639493777653)(imgs/1616577785188.png)] |
連接查詢映射配置 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZEw21kXy-1639493777653)(imgs/1616578573302.png)] |
子查詢映射配置 |
---|
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cJXyjnVq-1639493777654)(imgs/1616579097777.png)] |
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZO9RVUrd-1639493777654)(imgs/1616579157655.png)] |
十二、動態SQL
交友網:珍愛網、百合網 篩選心儀對象 性別 年齡 城市 身高
電商:淘寶、京東 篩選商品 羽毛球拍 品牌 價格
? 方江鵬 性別 女 select * from members where gender=‘女’
? 羅彪 性別 女 年齡 18-23 select * from members where gender=‘女’ and age >=18 and age <=23
? 張三 年齡 城市 select * from members where age >=18 and age <=23 and city=’’
用戶的篩選條件不同,我們完成篩選執行的SQL也不一樣;我們可以通過窮舉來一一的完成不同條件的篩選,但是這種實現思路過于繁瑣和復雜,MyBatis就提供了動態SQL的配置方式來實現多條件查詢。
12.1 什么是動態SQL?
根據查詢條件動態完成SQL的拼接
12.2 動態SQL使用案例
案例:心儀對象搜索
12.2.1 創建數據表
create table members(member_id int primary key auto_increment,member_nick varchar(20) not null unique,member_gender char(2) not null,member_age int not null,member_city varchar(30) not null
);
12.2.2 創建實體類
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Member {private int memberId;private String memberNick;private String memberGender;private int memberAge;private String memberCity;
}
12.2.3 創建DAO接口
在DAO接口中定義一個多條件查詢的方法
public interface MemberDAO {//在多條件查詢中,如果查詢條件不確定,可以直接使用HashMap作為參數//優點:無需單獨定義傳遞查詢條件的類//缺點:當向Map中存放參數時,key必須與動態sql保持一致()//public List<Member> searchMember(HashMap<String,Object> params);// 也可以定義專門用于存放查詢條件的實體類存放參數//優點:設置參數時無需關注屬性名//缺點:需要單獨定義一個類來封裝參數public List<Member> searchMember(MemberSearchCondition params);
}
12.3 if
<resultMap id="memberMap" type="Member"><id column="member_id" property="memberId"/><result column="member_nick" property="memberNick"/><result column="member_gender" property="memberGender"/><result column="member_age" property="memberAge"/><result column="member_city" property="memberCity"/>
</resultMap><select id="searchMember" resultMap="memberMap">select member_id,member_nick,member_gender,member_age,member_cityfrom memberswhere 1=1<if test="gender != null"> <!--gender 就是參數對象的屬性/參數Map的key-->and member_gender=#{gender}</if><if test="minAge != null">and member_age >= #{minAge} <!-- > --></if><if test="maxAge != null">and member_age <= #{maxAge} <!-- < --></if><if test="city != null">and member_city = #{city}</if>
</select>
測試
@Test
public void testSearchMember() {HashMap<String,Object> params = new HashMap<String, Object>();params.put("gender","女");params.put("minAge",18);//params.put("maxAge",23);params.put("city","武漢");//-----------------------------------------------------------------------MemberSearchCondition params2 = new MemberSearchCondition();params2.setGender("女");//params2.setMinAge(21);//params2.setMaxAge(30);//params2.setCity("武漢");//==========================================================================MemberDAO memberDAO = MyBatisUtil.getMapper(MemberDAO.class);List<Member> members = memberDAO.searchMember(params2);for (Member m: members) {System.out.println(m);}
}
12.4 where
<select id="searchMember" resultMap="memberMap">select member_id,member_nick,member_gender,member_age,member_cityfrom members<where><if test="gender != null"> <!--gender 就是參數對象的屬性/參數Map的key-->and member_gender=#{gender}</if><if test="minAge != null">and member_age >= #{minAge} <!-- > --></if><if test="maxAge != null">and member_age <= #{maxAge} <!-- < --></if><if test="city != null">and member_city = #{city}</if></where>order by member_age
</select>
12.5 trim
<select id="searchMember" resultMap="memberMap">select member_id,member_nick,member_gender,member_age,member_cityfrom members<trim prefix="where" prefixOverrides="and | or" suffix="order by member_age"><if test="gender != null"> <!--gender 就是參數對象的屬性/參數Map的key-->and member_gender=#{gender}</if><if test="minAge != null">and member_age >= #{minAge} <!-- > --></if><if test="maxAge != null">and member_age <= #{maxAge} <!-- < --></if><if test="city != null">and member_city = #{city}</if></trim>
</select>
12.6 foreach
public interface MemberDAO {//查詢指定城市的會員public List<Member> searchMemberByCity(List<String> cities);
}
<select id="searchMemberByCity" resultMap="memberMap">select member_id,member_nick,member_gender,member_age,member_cityfrom members where member_city in<foreach collection="list" item="cityName" separator="," open="(" close=")">#{cityName}</foreach>
</select>
測試
@Test
public void searchMemberByCity() {List<String> cities = new ArrayList<String>();cities.add("廈門");cities.add("宜昌");MemberDAO memberDAO = MyBatisUtil.getMapper(MemberDAO.class);List<Member> members = memberDAO.searchMemberByCity(cities);for (Member m: members) {System.out.println(m);}
}
十三、模糊查詢
案例:根據昵稱查詢會員信息(模糊匹配 like)
13.1 模糊查詢實現
13.1.1 DAO
public interface MemberDAO {//根據昵稱查詢用戶信息——模糊查詢// 模糊查詢需要使用${}取值,與sql進行拼接// 在使用${}時,即使只有一個參數也需要使用@Param注解聲明參數的key(非String對象參數可以不用聲明)public List<Member> searchMemberByNick(@Param("keyWord") String keyWord);}
13.1.2 映射文件
<!--如果參數時String類型,需要parameterType聲明參數類型-->
<select id="searchMemberByNick" parameterType="java.lang.String" resultMap="memberMap">select member_id,member_nick,member_gender,member_age,member_cityfrom memberswhere member_nick like '%${keyWord}%'
</select>
13.1.3 測試
@Test
public void testSearchMemberByNick(){MemberDAO memberDAO = MyBatisUtil.getMapper(MemberDAO.class);List<Member> members = memberDAO.searchMemberByNick("花");for (Member m: members) {System.out.println(m);}
}
13.2 #{}和${}的區別
- ${key} 表示獲取參數,先獲取參數的值拼接到SQL語句中,再編譯執行SQL語句;可能引起SQL注入問題
- #{key} 表示獲取參數,先完成SQL編譯(預編譯),預編譯之后再將獲取的參數設置到SQL與中 ,可以避免SQL注入問題
十四、MyBatis日志配置
MyBatis做為一個封裝好的ORM框架,其運行過程我們沒辦法跟蹤,為了讓開發者了解MyBatis執行流程及每個執行步驟所完成的工作,MyBatis框架本身支持log4j日志框架,對運行的過程進行跟蹤記錄。我們只需對MyBatis進行相關的日志配置,就可以看到MyBatis運行過程中的日志信息。
14.1 添加日志框架依賴
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>
14.2 添加日志配置文件
- 在resources目錄下創建名為
log4j.properties
文件 - 在
log4j.properties
文件配置日志輸出的方式
# 聲明日志的輸出級別及輸出方式
log4j.rootLogger=DEBUG,stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# 定義日志的打印格式 %t 表示線程名稱 %5p 日志級別 %msg日志信息
log4j.appender.stdout.layout.ConversionPattern=[%t] %5p - %msg \:%m%n
14.3 日志信息的級別
在使用日志框架輸出日志信息的時候,會根據輸出的日志信息的重要程度分為5個級別
級別 | 說明 |
---|---|
DEBUG | 輸出調試信息 |
INFO | 輸出提示信息 |
WARN | 輸出警告信息 |
ERROR | 一般性錯誤信息 |
FATAL | 致命性錯誤信息 |
十五、配置數據庫連接池-整合Druid
MyBatis做為一個ORM框架,在進行數據庫操作時是需要和數據庫連接連接的,MyBatis支持基于數據庫連接池的連接創建方式。
當我們配置MyBatis數據源時,只要配置了dataSource標簽的type屬性值為POOLED時,就可以使用MyBatis內置的連接池管理連接。
如果我們想要使用第三方的數據庫連接池,則需進行自定義配置。
15.1 常見的連接池
- DBCP
- C3P0
- Druid 性能也比較好,提供了比較便捷的監控系統
- Hikari 性能最好
功能 | dbcp | druid | c3p0 | HikariCP |
---|---|---|---|---|
是否支持PSCache | 是 | 是 | 是 | 否 |
監控 | jmx | jmx/log/http | jmx,log | jmx |
擴展性 | 弱 | 好 | 弱 | 弱 |
sql攔截及解析 | 無 | 支持 | 無 | 無 |
代碼 | 簡單 | 中等 | 復雜 | 簡單 |
更新時間 | 2015.8.6 | 2015.10.10 | 2015.12.09 | 2015.12.3 |
特點 | 依賴于common-pool | 阿里開源,功能全面 | 歷史久遠,代碼邏輯復雜,且不易維護 | 優化力度大,功能簡單,起源于boneCP |
連接池管理 | LinkedBlockingDeque | 數組 | threadlocal+CopyOnWriteArrayList |
15.2 添加Druid依賴
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.5</version>
</dependency>
15.3 創建Druid連接池工廠
public class DruidDataSourceFactory extends PooledDataSourceFactory {public DruidDataSourceFactory() {this.dataSource = new DruidDataSource();}
}
15.4 將DruidDataSourceFactory配置給MyBatis數據源
<environments default="mysql"><environment id="mysql"><transactionManager type="JDBC"></transactionManager><!-- POOLED 使用MyBatis內置的連接池實現 --><!-- mybatis需要一個連接池工廠,這個工廠可以產生數據庫連接池 PooledDataSourceFactory --><dataSource type="com.qfedu.utils.DruidDataSourceFactory"><property name="driverClass" value="${driver}"/><property name="jdbcUrl" value="${url}"/><property name="username" value="${username}"/><property name="password" value="${password}"/></dataSource></environment>
</environments>
十六、MyBatis緩存
MyBatis是基于JDBC的封裝,使數據庫操作更加便捷;MyBatis除了對JDBC操作步驟進行封裝之外也對其性能進行了優化:
- 在MyBatis引入緩存機制,用于提升MyBatis的檢索效率
- 在MyBatis引入延遲加載機制,用于減少對數據庫不必要的訪問
16.1 緩存的工作原理
緩存,就是存儲數據的內存
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ANrr7onU-1639493777655)(imgs/1616654926110.png)]
16.2 MyBatis緩存
MyBatis緩存分為一級緩存和二級緩存
16.2.1 一級緩存
一級緩存也叫做SqlSession級緩存,為每個SqlSession單獨分配的緩存內存,無需手動開啟可直接使用;多個SqlSession的緩存是不共享的。
特性:
1.如果多次查詢使用的是同一個SqlSession對象,則第一次查詢之后數據會存放到緩存,后續的查詢則直接訪問緩存中存儲的數據;
2.如果第一次查詢完成之后,對查詢出的對象進行修改(此修改會影響到緩存),第二次查詢會直接訪問緩存,造成第二次查詢的結果與數據庫不一致;
3.當我們進行在查詢時想要跳過緩存直接查詢數據庫,則可以通過sqlSession.clearCache();來清除當前SqlSession的緩存;
4.如果第一次查詢之后第二查詢之前,使用當前的sqlsession執行了修改操作,此修改操作會使第一次查詢并緩存的數據失效,因此第二次查詢會再次訪問數據庫。
測試代碼:
@Test
public void testQueryMemberById(){SqlSession sqlSession1 = MyBatisUtil.getSqlSessionFactory().openSession();SqlSession sqlSession2 = MyBatisUtil.getSqlSessionFactory().openSession();MemberDAO memberDAO1 = sqlSession1.getMapper(MemberDAO.class);Member member1 = memberDAO1.queryMemberById(1);System.out.println(member1);member1.setMemberAge(99);sqlSession1.clearCache();System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");MemberDAO memberDAO2 = sqlSession1.getMapper(MemberDAO.class);Member member2 =memberDAO2.queryMemberById(1);System.out.println(member2);
}
16.2.2 兩次查詢與數據庫數據不一致問題
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-D5lZ6Fzq-1639493777655)(imgs/1616660132980.png)]
16.2.3 二級緩存
二級緩存也稱為SqlSessionFactory級緩存,通過同一個factory對象獲取的Sqlsession可以共享二級緩存;在應用服務器中SqlSessionFactory是單例的,因此我們二級緩存可以實現全局共享。
特性:
1.二級緩存默認沒有開啟,需要在mybatis-config.xml中的settings標簽開啟
2.二級緩存只能緩存實現序列化接口的對象
- 在mybatis-config.xml開啟使用二級緩存
<settings><setting name="cacheEnabled" value="true"/>
</settings>
- 在需要使用二級緩存的Mapper文件中配置cache標簽使用功能二級緩存
<cache/>
- 被緩存的實體類實現序列化接口
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Member implements Serializable {private int memberId;private String memberNick;private String memberGender;private int memberAge;private String memberCity;
}
- 測試
@Test
public void testQueryMemberById(){SqlSessionFactory factory =MyBatisUtil.getSqlSessionFactory();// 1.多個SqlSession對象必須來自于同一個SqlSessionFactorySqlSession sqlSession1 = factory.openSession(true);SqlSession sqlSession2 = factory.openSession(true);System.out.println(sqlSession1 == sqlSession2);MemberDAO memberDAO1 = sqlSession1.getMapper(MemberDAO.class);Member member1 = memberDAO1.queryMemberById(1);System.out.println(member1);sqlSession1.commit(); //2.第一次查詢之后執行sqlSession1.commit(),會將當前sqlsession的查詢結果緩存到二級緩存System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");MemberDAO memberDAO2 = sqlSession2.getMapper(MemberDAO.class);Member member2 =memberDAO2.queryMemberById(1);System.out.println(member2);
}
16.3 查詢操作的緩存開關
<select id="queryMemberById" resultMap="memberMap" useCache="false">select member_id,member_nick,member_gender,member_age,member_cityfrom memberswhere member_id=#{mid}
</select>
十七、延遲加載
延遲加載——如果在MyBatis開啟了延遲加載,在執行了子查詢(至少查詢兩次及以上)時,默認只執行第一次查詢,當用到子查詢的查詢結果時,才會觸發子查詢的執行;如果無需使用子查詢結果,則子查詢不會執行.
開啟延遲加載:
<resultMap id="classMap" type="Clazz"><id column="cid" property="classId"/><result column="cname" property="className"/><result column="cdesc" property="classDesc"/><collection property="stus" select="com.qfedu.dao.StudentDAO.queryStudentsByCid" column="cid" fetchType="lazy"/>
</resultMap><select id="queryClassByCid" resultMap="classMap">select cid,cname,cdescfrom classeswhere cid=#{cid}
</select>
測試代碼:
@Test
public void queryClassByCid() {ClassDAO classDAO = MyBatisUtil.getMapper(ClassDAO.class);Clazz clazz = classDAO.queryClassByCid(1);System.out.println(clazz.getClassName());System.out.println("-----------------------------------");System.out.println(clazz.getStus());
}
運行日志:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2B6mC1Gv-1639493777656)(imgs/1616663783657.png)]
練習任務
jsp+servlet+
mybatis
商品信息的CRUD
- 添加商品
- 商品列表+分頁
- 刪除商品
- 修改商品
- 商品詳情