柚子快報激活碼778899分享:尚硅谷MyBatis 基礎(chǔ)筆記
MyBatis 筆記
文章目錄
MyBatis 筆記【1】配置文件【2】JDK Logging【3】Log4j【4】Log4j2【5】SLF4j【6】整合日志【7】SQL參數(shù)【8】${} / #{} 區(qū)別【9】MyBatis中DML操作【10】MyBatis中DQL操作【11】分頁查詢【12】模糊查詢【13】別名【14】結(jié)果填充【15】接口綁定【16】主鍵回填【17】動態(tài)SQL【18】常用注解【19】多表查詢【20】延遲加載【21】緩存【23】四大核心接口介紹及執(zhí)行流程【24】自定義插件【25】執(zhí)行器類型【26】執(zhí)行原理
【1】配置文件
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
【2】JDK Logging
package com.bjsxt;
import java.util.logging.*;
/**
* JDK自帶的日志 存在于java.util.logging包中
*/
public class TestJDKLogging {
public static void main(String[] args) throws Exception {
// 創(chuàng)建日志處理對象
Logger logger = Logger.getLogger("JDKLogger");
// 設(shè)置級別
logger.setLevel(Level.FINEST);
// 創(chuàng)建控制臺日志處理器
ConsoleHandler consoleHandler = new ConsoleHandler();
// 設(shè)置格式
consoleHandler.setFormatter(new SimpleFormatter());
// 設(shè)置日志級別
consoleHandler.setLevel(Level.FINEST);
// 增加到日志處理器
logger.addHandler(consoleHandler);
/*// 獲取控制臺日志處理器
Handler[] handlers = logger.getHandlers();
// 數(shù)組的0下標(biāo)就是控制器處理器
Handler handler = handlers[0];
// 設(shè)置控制臺日志級別
handler.setLevel(Level.FINEST);*/
// 創(chuàng)建一個文件日志處理器, 參數(shù)就是保存日志信息的文件名
FileHandler fileHandler = new FileHandler("jdk_log.log");
// 設(shè)置日志內(nèi)容的格式
fileHandler.setFormatter(new SimpleFormatter());
// 設(shè)置日志級別
fileHandler.setLevel(Level.FINEST);
// 把創(chuàng)建好的文件日志處理器,增加到日志處理對象中
logger.addHandler(fileHandler);
// 日志處理器,可以把日志信息輸出到控制臺和文件中
logger.finest("最詳細(xì)的日志");
logger.fine("詳細(xì)日志");
logger.warning("警告日志");
logger.info("標(biāo)準(zhǔn)消息日志");
logger.severe("嚴(yán)重錯誤日志");
}
}
【3】Log4j
# log4j中定義的級別:fatal(致命錯誤) > error(錯誤) >warn(警告) >info(普通信息) >debug(調(diào)試信息)>trace(跟蹤信息)
# 定義Log4j中的Logger工具中有什么handler。 第一個DEBUG代表的是日志的級別。 后續(xù)每個單詞是一個handler名稱
log4j.rootLogger = DEBUG , console , D
# log4j.logger是固定的,a.b.c是命名空間的名字可以只寫一部分。
# 是mybatis中要使用日志的時候,必須提供的配置。
# 代表sql配置文件的namespace相應(yīng)的sql,執(zhí)行的時候,是否輸出日志,及日志的級別。
# 如:namespace="a.b" , log4j.logger.a.b=XXX級別。 那么 a.b.*所有的sql運(yùn)行的時候,都輸出日志。建議
# 如:namespace="a.b" , log4j.logger.a=XXX級別, 那么 a.*.*所有的sql運(yùn)行,都輸出日志。不建議
# 如果有多個namespace都需要輸出日志。定義若干行即可
log4j.logger.a=TRACE
log4j.logger.a.b=TRACE
log4j.logger.a.c=TRACE
### console ###
# 開始定義console名稱的handler
# console handler的類型
log4j.appender.console = org.apache.log4j.ConsoleAppender
# console handler輸出的日志到哪里。 System.out 控制臺
log4j.appender.console.Target = System.out
# console輸出日志的時候,格式是什么樣的。是Pattern格式。 正則表達(dá)式格式
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 具體的格式正則規(guī)則。 %p 線程|進(jìn)程 %-d 時間 {yyyy-MM-dd HH\:mm\:ss} 具體的格式 %C 類名輸出日志的類型名 %m 輸出的日志文本 %n回車
log4j.appender.console.layout.ConversionPattern = [%p] [%-d{yyyy-MM-dd HH\:mm\:ss}] %C.%M(%L) | %m%n
### log file ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
# 文件日志handler,記錄日志到哪一個文件
log4j.appender.D.File = D:/log4j.log
# 是否是追加寫入到日志文件
log4j.appender.D.Append = true
# 只能升級別,不能降
# 名字是D的handler,自定義日志級別是什么。
log4j.appender.D.Threshold = INFO
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = [%p] [%-d{yyyy-MM-dd HH\:mm\:ss}] %C.%M(%L) | %m%n
package com.bjsxt;
import org.apache.log4j.Logger;
/**
* 測試Log4j
* 要求classpath下,必須有配置文件log4j.properties。且必須在classpath下。不能有其他目錄。
*/
public class TestLog4j {
public static void main(String[] args) {
Logger logger = Logger.getLogger(TestLog4j.class);
// 輸出各種日志
logger.info("info日志");
logger.warn("waring日志");
logger.error("error日志");
logger.debug("debug日志");
}
}
【4】Log4j2
package com.bjsxt;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* 測試Log4j2
*/
public class TestLog4j2 {
public static void main(String[] args) {
Logger logger = LogManager.getLogger(TestLog4j2.class);
// 輸出日志
logger.debug("debug信息");
logger.info("info");
logger.warn("waring");
logger.error("error");
logger.fatal("fatal");
}
}
【5】SLF4j
SLF4j是日志的接口聲明,不參與日志的具體實現(xiàn)。需要配合其他日志具體實現(xiàn)工具包才能進(jìn)行使用。每次添加其他日志工具包時,不要忘記SLF4j整合這個日志的依賴。
SLF4J支持多種日志實現(xiàn),但是在項目中整合依賴最好只有一個,否則會出現(xiàn)警告
# Log4j配置
log4j.rootLogger=ERROR, stdout
# log4j.logger是固定的,a.b.c是命名空間的名字可以只寫一部分。
log4j.logger.a.b.c=TRACE
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
public class Test {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Test.class);
logger.trace("trace");
logger.debug("debug");
logger.info("info");
logger.warn("warn");
logger.error("error");
}
}
【6】整合日志
MyBatis框架內(nèi)置日志工廠。日志工廠負(fù)責(zé)自動加載項目中配置的日志。MyBatis支持以下日志,當(dāng)存在多個日志工具時,嚴(yán)格按照從上往下順序使用,且只會使用一個
- SLF4J
- Apache Commons Logging
- Log4j 2
- Log4j (deprecated since 3.5.9)
- JDK logging
【7】SQL參數(shù)
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
insert into people values(default,#{suiyixie})
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
insert into tb_user(id, name) values(#{u1.id}, #{u2.name})
【8】${} / #{} 區(qū)別
#{} 被解析為?,用在設(shè)置列的值或條件值時,也可以使用在分頁等需要設(shè)置具體值的情況
${} 表示字符串拼接,用在動態(tài)設(shè)置表名和動態(tài)設(shè)置列名的情況下
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
insert into ${tableName} (${idColumn}, ${nameColumn}) values (#{id}, #{name})
【9】MyBatis中DML操作
操作mapper標(biāo)簽SqlSession方法名新增
在MyBatis中,增刪改底層使用的邏輯是同一個。那么在沒有特定需求的時候,可以使用update實現(xiàn)增刪改的所有功能。也就是標(biāo)簽用update, 方法用update。
雖然方便,但不推薦。語義不明確,后期維護(hù)的時候,成本高。
【10】MyBatis中DQL操作
SqlSession方法名解釋說明selectOne()查詢一行數(shù)據(jù)時,返回值為Object。如果沒有查詢到返回Null,但是不能查詢到多行會報錯。selectList()當(dāng)查詢多行數(shù)據(jù)時,返回值為List。如果沒有查詢到返回長度為零的List對象。selectMap()查詢多行數(shù)據(jù)時,把其中某列結(jié)果當(dāng)做key,每行結(jié)果為ValueselectCursor()使用游標(biāo)查詢時使用,在大量數(shù)據(jù)時可以代替分頁select()萬能方法,需要自己定義結(jié)果處理器
package com.bjsxt.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
/**
* 工具類型
*/
public final class MyBatisUtils {
// 會話工廠
private static SqlSessionFactory sqlSessionFactory;
/**
* 初始化代碼塊,只允許一次,類加載時運(yùn)行
*/
static {
// 創(chuàng)建會話工廠
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.cfg.xml"));
} catch (Exception e) {
e.printStackTrace();
// 拋出異常,代表工廠創(chuàng)建失敗,后續(xù)的獲取會話,訪問數(shù)據(jù)庫,都不可使用。
// 讓代碼終止
throw new ExceptionInInitializerError(e);
}
}
/**
* 私有構(gòu)造方法,禁止外部創(chuàng)建對象
*/
private MyBatisUtils() {
}
/**
* 獲取工廠的工具方法
*
* @return
*/
public static SqlSessionFactory getFactory() {
return sqlSessionFactory;
}
}
package com.bjsxt;
import com.bjsxt.pojo.User;
import com.bjsxt.utils.MyBatisUtils;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.result.DefaultResultHandler;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 查詢
*/
public class TestSelect {
/**
* 查詢方法
*
* selectOne selectList 最多 。 必須掌握
* selectMap 次之。 建議掌握
* selectCursor 再次。 了解
* select 最少。 聽說
* 上述方法的使用頻率,就是定義順序。
*/
/**
* 查詢多行數(shù)據(jù)
*/
/**
* 使用select方法查詢多行數(shù)據(jù)
* void select(String statement[, Object parameter], ResultHandler handler);
* ResultHandler參數(shù) - 處理查詢結(jié)果的處理器。查詢的結(jié)果從處理器中獲取。
* 是一個接口。只有唯一的一個方法 void handleResult(ResultContext context)
* ResultContext中,有方法,可以獲取一個對象。ResultContext,就是查詢的一行數(shù)據(jù)。
* 獲取的對象,就是這行數(shù)據(jù)轉(zhuǎn)換后的Java對象。
* MyBatis給接口ResultHandler提供了若干實現(xiàn)類,select方法的底層實現(xiàn)就是selectList。
* select方法,只能查詢多行數(shù)據(jù),且返回List集合。
* 如果需要做特殊定制處理,可以給接口增加新的實現(xiàn)類。
* 使用的ResultHandler實現(xiàn)類型模式是DefaultResultHandler
*
* 使用頻率最低。幾乎不用。
*/
@Test
public void testSelect() {
SqlSession session = MyBatisUtils.getFactory().openSession();
DefaultResultHandler resultHandler = new DefaultResultHandler();
session.select("user.mapper.selectAll", resultHandler);
// 查詢結(jié)果后,從ResultHandler中獲取結(jié)果
List list = resultHandler.getResultList();
list.forEach(obj -> {
System.out.println(obj);
});
session.close();
}
/**
* 使用selectCursor查詢多行數(shù)據(jù)
*
* MyBatis 中 selectCursor方法返回的游標(biāo),就是JDBC查詢結(jié)果游標(biāo)。
*
* 游標(biāo)特性: 在MyBatis中,使用Cursor游標(biāo)獲取結(jié)果前,不能關(guān)閉SqlSession。否則拋出異常。
* 1. 必須保證連接未關(guān)閉。
* 2. 必須保證會話未結(jié)束。 會話是一個持續(xù)的,有狀態(tài)的,連接或數(shù)據(jù)對象。
* 如: 連接會話, Connection, 是一個持續(xù)的,不關(guān)閉一直可以使用。 有狀態(tài)的, connection創(chuàng)建后,關(guān)閉前,操作都是有狀態(tài)的,
* connection是跟著操作一起變化的, 是可能有數(shù)據(jù)變動的。
* 如: 數(shù)據(jù)會話, HttpSession。請求到來,創(chuàng)建會話,相應(yīng)返回,連接斷開,關(guān)閉。連接關(guān)閉,會話還存在。
*
* 為什么不能關(guān)閉會話后,再迭代Cursor。
* Cursor中數(shù)據(jù),不再Java的管理內(nèi)存中,在數(shù)據(jù)庫的管理內(nèi)存中。
* 每次迭代Cursor中的一條數(shù)據(jù),必須連接數(shù)據(jù)庫,從數(shù)據(jù)庫管理的內(nèi)存中,把數(shù)據(jù)讀取到Java管理的內(nèi)存中,再使用。
*
* Cursor - 游標(biāo)
* JDBC查詢數(shù)據(jù)庫時,返回的結(jié)果ResultSet中,保存查詢結(jié)果數(shù)據(jù)的,就是游標(biāo)。
* 查詢的SQL沒有變化。
*
* 不常用
* 查詢的數(shù)據(jù)結(jié)果數(shù)量特別多,對Java代碼運(yùn)行的電腦內(nèi)存壓力非常大,且對查詢結(jié)果只做部分處理時,使用。
*/
@Test
public void testSelectCursor() {
Map
params.put("start", 100);
params.put("end", 1000);
SqlSession session = MyBatisUtils.getFactory().openSession();
// 查詢?nèi)繑?shù)據(jù)
Cursor
// 條件查詢部分?jǐn)?shù)據(jù)
Cursor
session.close();
cursor.forEach(user -> {
System.out.println(user);
});
cursor1.forEach(user -> {
System.out.println(user);
});
}
/**
* 使用 selectMap 查詢多行數(shù)據(jù)
*
* keyColumnName 參數(shù) - 使用哪一個字段的值作為返回結(jié)果Map集合的key。
*/
@Test
public void testSelectMap() {
// 使用字段name的值作為key
SqlSession session = MyBatisUtils.getFactory().openSession();
// 查詢?nèi)?/p>
Map
Map
params.put("start", 100);
params.put("end", 1000);
// 根據(jù)主鍵范圍查詢
Map
session.close();
System.out.println(map1.size());
for (Map.Entry
System.out.println("key = " + entry.getKey() + " ; value = " + entry.getValue());
}
System.out.println(map2.size());
for (Map.Entry
System.out.println("key = " + entry.getKey() + " ; value = " + entry.getValue());
}
}
/**
* 使用 selectList查詢多行數(shù)據(jù)
*
* 特性:
* 1. 查詢無結(jié)果,返回size() = 0的 集合對象
* 2. 查詢有結(jié)果,返回size() = 查詢結(jié)果行數(shù)的 集合對象。
*/
@Test
public void testSelectAll() {
SqlSession session = MyBatisUtils.getFactory().openSession();
List
System.out.println(users.size());
users.forEach(user -> {
System.out.println(user);
});
}
@Test
public void testSelectRangeId() {
Map
params.put("start", 10);
params.put("end", 100);
SqlSession session = MyBatisUtils.getFactory().openSession();
List
System.out.println(list1.size());
list1.forEach(u -> {
System.out.println(u);
});
params.put("start", -10);
params.put("end", -1);
List
System.out.println(list2.size());
list2.forEach(user -> {
System.out.println(user);
});
}
/**
* 查詢一行數(shù)據(jù)
* 調(diào)用方法 :
* 方法特性:
* 1. 查詢結(jié)果不存在,返回null
* 2. 查詢結(jié)果只有一行,返回對象
* 3. 查詢結(jié)果多于一行,拋出異常
*/
@Test
public void testSelectById() {
SqlSession session = MyBatisUtils.getFactory().openSession();
User user = session.selectOne("user.mapper.selectById", 1000);
System.out.println(user);
session.close();
}
@Test
public void testSelectByName() {
String name = "劉備";
SqlSession session = MyBatisUtils.getFactory().openSession();
User u1 = session.selectOne("user.mapper.selectByName", name);
System.out.println(u1);
name = "大小姐";
User u2 = session.selectOne("user.mapper.selectByName", name);
System.out.println(u2);
name = "張三";
User u3 = session.selectOne("user.mapper.selectByName", name);
System.out.println(u3);
}
}
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
select id, name
from tb_user
select id, name
from tb_user
where id between #{start} and #{end}
select id, name
from tb_user
where id = #{id}
select id, name
from tb_user
where name = #{name}
select count(1) from tb_student
【11】分頁查詢
RowBounds是MyBatis中提供的一種"假分頁"實現(xiàn)方式。對從數(shù)據(jù)庫中查詢到的結(jié)果進(jìn)行截取。所以如果數(shù)據(jù)庫中數(shù)據(jù)量特別大的時候可能會出現(xiàn)OOM等問題
但是由于RowBounds不需要考慮底層數(shù)據(jù)庫是什么,且使用簡單,所以對于一些數(shù)據(jù)量不是特別大的應(yīng)用還是有人選擇使用的
在SqlSession中select、selectMap、selectList中通過方法重載都提供了一個帶有RowBounds
/**
* 分頁查詢:
* 分頁查詢的種類|方式(面試題):
* 1. 物理分頁,使用分頁查詢語法實現(xiàn)分頁查詢。如:MySQL數(shù)據(jù)庫中的limit; SqlServer數(shù)據(jù)庫中的top; Oracle數(shù)據(jù)庫中的rownum+子查詢
* 可移植性差,和數(shù)據(jù)庫耦合,因為不同的數(shù)據(jù)庫軟件,分頁語法不同。
* 相對效率更好,因為數(shù)據(jù)庫的分頁是有特殊優(yōu)化的,是數(shù)據(jù)庫廠商優(yōu)化的。且網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)少,更可靠。
* 2. 邏輯分頁,不使用分頁查詢語法,是查詢?nèi)繑?shù)據(jù),在內(nèi)存中,使用某種邏輯,分頁返回。
* 特點和物理分頁相反。
* MyBatis中的分頁實現(xiàn):
* 1. 物理分頁,使用分頁語法實現(xiàn)分頁查詢。
* 2. RowBounds分頁,是邏輯分頁的一種實現(xiàn)。底層基于Cursor實現(xiàn)。
* 查詢后,依次遍歷Cursor,按照分頁的需求,找到對應(yīng)的若干數(shù)據(jù),之后關(guān)閉Cursor,返回分頁結(jié)果。
*/
/**
* RowBounds分頁
* selectList * 不推薦使用,相對效率較低。 */ @Test public void testRowBounds() { SqlSession session = MyBatisUtils.getFactory().openSession(); int page = 1; int rows = 3; RowBounds rowBounds = new RowBounds((page - 1) * rows, rows); List users.forEach(user -> { System.out.println(user); }); System.out.println("===================================================="); page = 7; rowBounds = new RowBounds((page - 1) * rows, rows); users = session.selectList("user.mapper.selectAll", null, rowBounds); users.forEach(user -> { System.out.println(user); }); } 功能實現(xiàn) package com.bjsxt.result; import com.bjsxt.pojo.Student; import java.io.Serializable; import java.util.List; import java.util.Objects; /** * 分頁查詢結(jié)果類型 */ public class PageResult implements Serializable { // 當(dāng)前是第幾頁 private int currPage; // 一共多少行數(shù)據(jù) private int total; // 當(dāng)前頁要顯示的數(shù)據(jù)集合 private List // 每頁多少行數(shù)據(jù) private int pageSize; /** * 增加推導(dǎo)屬性,一共多少頁。 可選 * JavaScript 中,可以使用Math.ceil(total / pageSize) 向上取整計算總計頁數(shù) */ public void setPages(int pages) { } public int getPages() { return total % pageSize == 0 ? total / pageSize : (total / pageSize + 1); } public PageResult() { } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PageResult that = (PageResult) o; return currPage == that.currPage && total == that.total && pageSize == that.pageSize && Objects.equals(list, that.list); } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } @Override public int hashCode() { return Objects.hash(currPage, total, list); } public int getCurrPage() { return currPage; } public void setCurrPage(int currPage) { this.currPage = currPage; } public int getTotal() { return total; } public void setTotal(int total) { this.total = total; } public List return list; } public void setList(List this.list = list; } } package com.bjsxt.service; import com.bjsxt.param.MyParam; import com.bjsxt.result.PageResult; import java.util.Map; public interface StudentService { /** * 分頁查詢 * @param param 查詢條件,由Servlet處理后傳入 * @return 自定義一個結(jié)果類型返回。 * 分頁結(jié)果需要內(nèi)容有: * 1. 第幾頁 * 2. 總計多少行數(shù)據(jù) * 3. 一共多少頁 * 4. 當(dāng)前頁要顯示的數(shù)據(jù)集合 */ PageResult selectByPage(MyParam param); } package com.bjsxt.service.impl; import com.bjsxt.dao.StudentDao; import com.bjsxt.dao.impl.StudentDaoImpl; import com.bjsxt.param.MyParam; import com.bjsxt.pojo.Student; import com.bjsxt.result.PageResult; import com.bjsxt.service.StudentService; import java.util.List; /** * 服務(wù)邏輯實現(xiàn)類型 */ public class StudentServiceImpl implements StudentService { // 需要的數(shù)據(jù)庫訪問對象 private StudentDao studentDao = new StudentDaoImpl(); /** * 分頁查詢 * @param param 查詢條件,由Servlet處理后傳入 * @return */ @Override public PageResult selectByPage(MyParam param) { // 查詢當(dāng)前頁要顯示的數(shù)據(jù)集合 List // 查詢總計多少行數(shù)據(jù) int total = studentDao.selectCount(param); // 根據(jù)查詢結(jié)果,創(chuàng)建要返回的結(jié)果 PageResult pageResult = new PageResult(); pageResult.setCurrPage(param.getPageNum()); pageResult.setPageSize(param.getPageSize()); pageResult.setList(students); pageResult.setTotal(total); // 返回 return pageResult; } } package com.bjsxt.controller; import com.bjsxt.param.MyParam; import com.bjsxt.result.PageResult; import com.bjsxt.service.StudentService; import com.bjsxt.service.impl.StudentServiceImpl; import com.fasterxml.jackson.databind.ObjectMapper; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 分頁查詢學(xué)生的Servlet 控制器 */ @WebServlet("/getStudent") public class StudentPageServlet extends HttpServlet { private StudentService studentService = new StudentServiceImpl(); /** * servlet 服務(wù)方法 * 參數(shù)字符集問題: * 1. 檢查JSP的字符集 * 2. 檢查Servlet中的請求字符集 * 3. 檢查請求方式是否是POST。request.setCharacterEncoding只對請求體中的數(shù)據(jù)生效 * * HTTP協(xié)議的請求頭,默認(rèn)字符集永遠(yuǎn)是ISO-8859-1。除非修改Tomcat中的server.xml配置文件,增加URIEncoding配置 * 請求頭參數(shù),絕大多數(shù)處理方案是:解碼再編碼 * * @param req * @param resp * @throws ServletException * @throws IOException */ @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); // 處理請求參數(shù), // 如果未傳遞參數(shù) http://localhost:80/,返回結(jié)果是null // 如果傳遞參數(shù)未賦值 http://localhost:80/pageNum=&pageSize&name&address,返回結(jié)果是空字符串 "" // 分頁的頁碼 String pageNum = req.getParameter("pageNum"); // 分頁后的每頁行數(shù) String pageSize = req.getParameter("pageSize"); // 姓名模糊查詢條件 String name = req.getParameter("name"); byte[] nameBytes = name.getBytes("ISO-8859-1"); // 把字符串按照指定字符集,解碼成字節(jié)數(shù)組 name = new String(nameBytes, "UTF-8"); // 把字節(jié)數(shù)組,按照指定的字符集,編碼成字符串。 // 地址模糊查詢條件 String address = req.getParameter("address"); address = new String(address.getBytes("ISO-8859-1"), "UTF-8"); // 維護(hù)查詢條件對象 MyParam param = new MyParam(); param.setName((name == null || "".equals(name) ? null : name)); param.setAddress((address == null || "".equals(address)) ? null : address); param.setPageNum((pageNum == null || "".equals(pageNum)) ? 1 : Integer.parseInt(pageNum)); param.setPageSize((pageSize == null || "".equals(pageSize)) ? 2 : Integer.parseInt(pageSize)); // 查詢 PageResult pageResult = studentService.selectByPage(param); // 向客戶端輸出的是一個對象,使用JSON格式的字符串描述這個對象。 // 設(shè)置響應(yīng)頭 resp.setContentType("application/json; charset=UTF-8"); // 設(shè)置相應(yīng)輸出流中的字符集 resp.setCharacterEncoding("UTF-8"); // 使用Jackson,把Java對象轉(zhuǎn)換成JSON格式的字符串。 // 創(chuàng)建Jackson中的對象和JSON互相轉(zhuǎn)換的工具對象。 ObjectMapper objectMapper = new ObjectMapper(); // 把java對象,轉(zhuǎn)換成JSON格式的字符串 String json = objectMapper.writeValueAsString(pageResult); // PrintWriter resp.getWriter() // 所有的IO輸出流的 PrintWriter & PrintStream,都有方法print和println resp.getWriter().println(json); // 刷新輸出流的緩沖區(qū) resp.getWriter().flush(); } } <%@ page contentType="text/html;charset=UTF-8" language="java" %>
序號 | 姓名 | 地址 | 操作 |
---|
【12】模糊查詢
/**
* 模糊查詢
* SQL: select 字段 from 表格 where 字段 like ?
* 條件是: _xxx xxx_ _xx_ %xxx xxx% %xxx% _xxx% %xxx_
* _ : 是一個字符
* % : 是若干字符
*
* MyBatis中 #{}內(nèi)不可以寫 '' 。所以模糊查詢需要做特殊處理。
* #{'%' + name + '%'}
*/
@Test
public void testLike3() {
/*
* 借助數(shù)據(jù)庫的字符串拼接函數(shù),實現(xiàn)模糊查詢。
* 缺點是: 需要了解數(shù)據(jù)庫的函數(shù),且不同的數(shù)據(jù)庫函數(shù)未必相同。
* 優(yōu)點: 更加符合開發(fā)邏輯。且沒有SQL注入隱患
* 推薦的模糊查詢方式
*/
String arg = "d";
SqlSession session = MyBatisUtils.getFactory().openSession();
List
users.forEach(user -> {
System.out.println(user);
});
session.close();
}
@Test
public void testLike2() {
/*
* 使用MyBatis中的${}實現(xiàn)字符串拼接??梢员苊鈪?shù)傳遞過程中的拼接問題。
* 但是可能有SQL注入隱患。 String arg = "d%' or 1 = 1 or name like '%n";
*/
String arg = "d";
SqlSession session = MyBatisUtils.getFactory().openSession();
List
users.forEach(user -> {
System.out.println(user);
});
session.close();
}
@Test
public void testLike1() {
/*
* 需要傳遞參數(shù)時,拼接必要的占位符,也就是 % 或 _
*/
String arg = "d";
SqlSession session = MyBatisUtils.getFactory().openSession();
List
users.forEach(user -> {
System.out.println(user);
});
session.close();
}
select id, name
from tb_user
where name like #{name}
select id, name
from tb_user
where name like '%${name}%'
select id, name
from tb_user
where name like concat('%', #{name}, '%')
【13】別名
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
MyBatis框架中內(nèi)置了一些常見類型的別名。這些別名不需要配置
別名映射的類型別名映射的類型別名映射的類型_bytebytestringStringdateDate_longlongbyteBytedecimalBigDecimal_shortshortlongLongbigdecimalBigDecimal_intintshortShortobjectObject_integerintintIntegermapMap_doubledoubleintegerIntegerhashmapHashMap_floatfloatdoubleDoublelistList_booleanbooleanfloatFloatarraylistArrayListbooleanBooleancollectionCollectioniteratorIterator
【14】結(jié)果填充
在MyBatis框架中,查詢結(jié)果處理方案(結(jié)果填充|結(jié)果映射)有若干種:
1. 自動映射 auto mapping : 當(dāng)
如:表格字段 id, name。類型 com.bjsxt.pojo.Xxx 屬性(field和property)是 id name。自動映射。
當(dāng)字段名和屬性名不同時,可以通過查詢語法的別名實現(xiàn)自動映射。
優(yōu)勢: 不需要做任何特殊的處理配置
缺點: 要求表格的字段名稱和類型的屬性名稱必須一致。 Java中的命名規(guī)范 駝峰。 數(shù)據(jù)庫命名規(guī)范 多單詞之間下劃線分隔,一般所有單詞全小寫。
2. 手工配置映射 ResultMap映射。 首選
需要在SQL配置文件中定義標(biāo)簽
優(yōu)勢: 一切自定義,靈活。
缺點: 配置增加
3. 駝峰對下劃線 很少使用
因為Java命名習(xí)慣 駝峰; 數(shù)據(jù)庫命名習(xí)慣 下劃線。因為這種常見的命名習(xí)慣很通用。所以MyBatis提供自動的駝峰對下劃線映射。
需要在mybatis.cfg.xml中開啟這種功能。
優(yōu)勢: 按照常見命名習(xí)慣自動映射
缺點: 要求表格命名和類型命名必須要個遵循常見命名習(xí)慣,并一一對應(yīng)。
【15】接口綁定
MyBatis提供自動的接口實現(xiàn)能力。稱為接口綁定。
命名習(xí)慣:
每個框架或技術(shù),都有各自的命名習(xí)慣。
MyBatis技術(shù)中,數(shù)據(jù)訪問層(持久層)接口命名習(xí)慣是 XxxMapper。
正式工作開發(fā)中,數(shù)據(jù)訪問層(持久層)接口命名習(xí)慣是 XxxDao, XxxDAO
接口綁定的規(guī)則:
1. 必要規(guī)則: 必須遵守的規(guī)則,有兩條
1.1 SQL配置文件namespace,必須和對應(yīng)的要生成實現(xiàn)的接口的全命名完全相同,大小寫敏感。
1.2 SQL配置文件中,編寫SQL語句的標(biāo)簽id,必須和對應(yīng)的接口中的方法名完全相同,大小寫敏感。
2. 可選規(guī)則: 可以遵守的規(guī)則,遵守可以簡化mybatis.cfg.xml中的配置。 有兩條
2.1 SQL配置文件存放的位置,和對應(yīng)的接口所在的包完全相同。
2.2 SQL配置文件的文件名(不包含后綴),和對應(yīng)的接口的類型名完全相同。
遵守這兩個可選規(guī)則,可以在mybatis.cfg.xml中,使用
接口綁定中的參數(shù)傳遞:
1. 一個參數(shù)
1.1 傳遞簡單參數(shù)。 SQL配置文件中的占位變量命名隨意??蚣懿粰z查。
1.2 傳遞Map參數(shù)。 SQL配置文件中的占位變量名必須是Map中的key。
1.3 傳遞自定義類型對象(POJO實體)。 SQL配置文件中的占位變量名必須和類型的屬性名(property或field)一致。
2. 多個參數(shù)
注意: 使用注解了argM的形式就不能使用了,需要通過注解中名稱或paramN的方式調(diào)用
2.1 傳遞多個簡單參數(shù)。 要求SQL配置文件中的占位變量必須按照框架要求定義。
2.1.1 如果接口的方法參數(shù),使用的注解@Param("")定義參數(shù)名,則占位變量名必須是注解的屬性值,或param1,param2,param3等。 推薦的方式
2.1.2 如果接口的方法參數(shù),不使用注解,則占位變量名必須是 param1,param2或 arg0, arg1 等。不推薦的方式。面試可能問。
2.2 傳遞多個參數(shù),帶有Map或自定義類型對象。 要求SQL配置文件的占位變量必須按照框架要求定義。
2.2.1 如果接口方法參數(shù),使用注解,可以使用 #{注解屬性.key}|#{注解屬性.屬性名} 或者 #{param1.key} | #{param1.屬性名}
2.2.2 如果接口方法參數(shù),不使用注解,可以使用 #{arg0.key} | #{arg0.屬性名} 或者 #{param1.key} | #{param1.屬性名}
多參數(shù)傳遞
int insert1(@Param("id") Integer id, @Param("name") String name);
int insert2(Integer id, String name);
int insert3(@Param("myMap") Map
int insert4(Map
insert into tb_people(id, name) values(#{param1}, #{name})
insert into tb_people(id, name) values(#{arg0}, #{param2})
insert into tb_people(id, name) values(#{param1.id}, #{peo.name})
insert into tb_people(id, name) values(#{arg0.id}, #{param2.name})
【16】主鍵回填
insert into tb_people(id, name) values(default, #{name})
【17】動態(tài)SQL
動態(tài)SQL在MyBatis中是基于標(biāo)簽實現(xiàn)的。
1. if標(biāo)簽。判斷標(biāo)簽。根據(jù)某boolean值,或結(jié)果為boolean的表達(dá)式(布爾表達(dá)式)動態(tài)判斷。
在MyBatis的SQL配置文件中,可以寫OGNL表達(dá)式。語法類似JSP中的EL表達(dá)式。在各種標(biāo)簽中使用,得到需要的結(jié)果。
2. choose標(biāo)簽。選擇標(biāo)簽。類似java語法中是switch case。根據(jù)多個不同的判斷邏輯,選擇某一個分支。
3. trim標(biāo)簽。為標(biāo)簽體中的文本去除前后綴或增加前后綴。先刪除前后綴,再增加前后綴
4. where標(biāo)簽。用在SQL語句的where子句動態(tài)處理中。如果標(biāo)簽中存在文本,則增加where子句。如果標(biāo)簽中不存在文本,則不增加where子句。
where標(biāo)簽,有動態(tài)識別能力,如果標(biāo)簽內(nèi)的文本以and或者or開頭,自動刪除前綴and或or。
5. set標(biāo)簽。用在update語句中的標(biāo)簽。動態(tài)增加set子句。 有自動識別能力,如果標(biāo)簽內(nèi)的文本以','結(jié)尾,自動刪除后綴','。
6. forEach標(biāo)簽。用于循環(huán)集合的標(biāo)簽。常用于,批量新增,范圍in|not in查詢等。
要循環(huán)的變量: 命名方式有
1. 使用注解@Param("名字"), 推薦使用
2. arg0, collection, list 可用于List, Collection
3. arg0, array 可用于 []
close="循環(huán)內(nèi)容的結(jié)束字符串" item="循環(huán)過程的變量名" separator="每次循環(huán)時間隔符">
close=")" item="a" separator=","> #{a}
循環(huán)遍歷后的結(jié)果文本是: (?, ?, ?)
7. bind 標(biāo)簽,用于綁定修改后的特定數(shù)據(jù)的。常用于模糊查詢處理。
8. sql和include標(biāo)簽。 sql標(biāo)簽,定義SQL語句片段。 include標(biāo)簽,引用定義好的SQL語法片段。
常用于定義字段列表,或復(fù)雜的判斷邏輯。
如:
十幾個if,+不同的查詢條件
if
select * from people where 1=1
and name=#{name}
and address=#{address}
choose
select id, name from tb_people
where 1 = 1
and id < 10
and id between 10 and 20
and id > 20
trim
select * from people
and name=#{name}
and address=#{address}
where
select * from people
and name=#{name}
and address=#{address}
set
update people
name=#{name},
address=#{address},
id=#{id}
where id = #{id}
foreach
select id, name from tb_people where id in
#{id}
insert into tb_people(id, name)
#{p.id},#{p.name}
/**
* 根據(jù)參數(shù),in查詢部分?jǐn)?shù)據(jù)
*
* @param ids
* @return
*/
List
/**
* 批量新增
*
* @param list
* @return
*/
int insertBatch(List
bind
select * from people where name like #{n}
sql 和 include
select
id,name,address
【18】常用注解
注解功能@Insert新增@Delete刪除@Update修改@Select查詢@Results / @Result結(jié)果映射@SelectKey主鍵回填@InsertProvider調(diào)用SQL構(gòu)建器。新增專用@DeleteProvider調(diào)用SQL構(gòu)建器。刪除專用@UpdateProvider調(diào)用SQL構(gòu)建器。修改專用@SelectProvider調(diào)用SQL構(gòu)建器。查詢專用@Param定義參數(shù)的名稱
常用注解:
1. 新增
@Insert("sql語句")
注解中處理參數(shù)的方式,和SQL配置文件中的方式完全相同。但是此注解沒有動態(tài)SQL標(biāo)簽的功能。
1.1 主鍵回填
@SelectKey(statement="sql", before=真假, keyProperty="主鍵屬性名"[, keyColumn="查詢主鍵的字段名"], resultType=類型)
2. 更新
@Update("sql"), 和SQL配置文件采用相同的參數(shù)處理方案。
3. 刪除
@Delete("sql")
4. 查詢
@Select("sql")
使用注解時,返回結(jié)果類型,默認(rèn)就是方法的返回值類型或返回值集合的泛型或Map。 相當(dāng)于SQL配置文件中的
5. 動態(tài)新增
@InsertProvider(type=Xxx.class, method="生成SQL語句的方法名")
6. 動態(tài)更新
@UpdateProvider()
7. 動態(tài)刪除
@DeleteProvider()
8. 動態(tài)查詢
@SelectProvider()
9. 結(jié)果映射處理
@Results( //相當(dāng)于resultMap標(biāo)簽, 沒有強(qiáng)制id, 類型就是方法返回類型或返回集合的泛型
@Result(column="字段名" property="屬性名"), // 配置類型中的一個屬性和一個字段的映射
@Result(column="字段名" property="屬性名")
)
使用注解:
1. 前提: 在mybatis.cfg.xml配置文件中,使用
2. 日志: 使用接口的包作為日志輸出的定位即可。更精確的方式是包名.接口名。
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
create table tb_dept(
dep_id int(11) primary key auto_increment comment '部門主鍵',
dep_name varchar(32) comment '部門名稱',
dep_addr varchar(255) comment '部門地址'
) comment '部門表';
insert into tb_dept(dep_id, dep_name, dep_addr)
values (default, '教學(xué)部', '賽蒂工業(yè)園'),
(default, '行政部', '賽蒂工業(yè)園'),
(default, '財務(wù)部', '賽蒂工業(yè)園');
package com.bjsxt.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
/**
* 工具類型
*/
public final class MyBatisUtils {
// 會話工廠
private static SqlSessionFactory sqlSessionFactory;
/**
* 初始化代碼塊,只允許一次,類加載時運(yùn)行
*/
static {
// 創(chuàng)建會話工廠
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.cfg.xml"));
} catch (Exception e) {
e.printStackTrace();
// 拋出異常,代表工廠創(chuàng)建失敗,后續(xù)的獲取會話,訪問數(shù)據(jù)庫,都不可使用。
// 讓代碼終止
throw new ExceptionInInitializerError(e);
}
}
/**
* 私有構(gòu)造方法,禁止外部創(chuàng)建對象
*/
private MyBatisUtils() {
}
/**
* 獲取工廠的工具方法
*
* @return
*/
public static SqlSessionFactory getFactory() {
return sqlSessionFactory;
}
}
package com.bjsxt.mapper;
import com.bjsxt.pojo.Dept;
import com.bjsxt.provider.MyInsertSqlProvider;
import org.apache.ibatis.annotations.*;
import java.util.List;
import java.util.Map;
/**
* 數(shù)據(jù)訪問接口 , 持久層接口
*/
public interface DeptMapper {
/**
* 查詢
* 當(dāng)id= null, 查詢?nèi)?/p>
* id < 10, 查詢 id = 2的數(shù)據(jù)
* id == 10, 查詢 id = 3的數(shù)據(jù)
* id > 10, 查詢 id = 4的數(shù)據(jù)
*
* @param id
* @return
*/
@SelectProvider(type = MyInsertSqlProvider.class, method = "select")
List
/**
* 批量新增
*
* @param list
* @return
*/
@InsertProvider(type = MyInsertSqlProvider.class, method = "suibian")
int insertBatch(List
@Results(value = { // {} 代表數(shù)組
@Result(column = "dep_id", property = "depId"), @Result(column = "dep_name", property = "depName"), @Result(column = "dep_addr", property = "depAddr")})
@Select("select dep_id, dep_name, dep_addr from tb_dept where dep_id = #{depId}")
Dept selectByResults(Integer id);
/**
* 查詢所有,返回結(jié)果每行封裝一個Map集合。key是字段名,value是字段值
*
* @return
*/
@Select("select dep_id , dep_name , dep_addr from tb_dept")
List
/**
* 查詢所有
*
* @return
*/
@Select("select dep_id as depId, dep_name as depName, dep_addr as depAddr from tb_dept")
List
/**
* 根據(jù)主鍵查詢
*
* @param id
* @return
*/
@Select("select dep_id as depId, dep_name as depName, dep_addr as depAddr from tb_dept where dep_id = #{depId}")
Dept selectById(Integer id);
/**
* 根據(jù)主鍵刪除
*
* @param id
* @return
*/
@Delete("delete from tb_dept where dep_id = #{depId}")
int deleteById(Integer id);
/**
* 根據(jù)主鍵更新其他字段
*
* @param dept
* @return
*/
@Update("update tb_dept set dep_name = #{depName}, dep_addr = #{depAddr} where dep_id = #{depId}")
int updateById(Dept dept);
/**
* 新增
*
* @param dept
* @return
*/
@Insert("insert into tb_dept(dep_id, dep_name, dep_addr) values(#{depId}, #{depName}, #{depAddr})")
@SelectKey(statement = "select @@identity as id", keyProperty = "depId", before = false, resultType = Integer.class, keyColumn = "id")
int insertDept(Dept dept);
}
package com.bjsxt.provider;
import com.bjsxt.pojo.Dept;
import org.apache.ibatis.jdbc.SQL;
import java.util.List;
/**
* 自定義的新增SQL語句處理類型
*/
public class MyInsertSqlProvider {
/**
* 動態(tài)生成SQL
* MyBatis提供了一個類型,可以通過調(diào)用方法的方式,逐漸拼接一個完整的SQL。
*
* @param id
* @return
*/
public String select(Integer id) {
// MyBatis提供的SQL生成類型
SQL s = new SQL();
// 拼接select語句,參數(shù)是若干字段
s.SELECT("dep_id as depId", "dep_name as depName", "dep_addr as depAddr").FROM("tb_dept");
if (id == null) {
return s.toString();
}
if (id < 10) {
s.WHERE("dep_id = 2");
return s.toString();
}
if (id == 10) {
s.WHERE("dep_id = 3");
return s.toString();
}
if (id > 10) {
s.WHERE("dep_id = 4");
return s.toString();
}
// 返回生成的SQL語句。
return s.toString();
}
/**
* 動態(tài)生成SQL的方法定義要求:
* 1. 公開
* 2. 名稱任意定義
* 3. 返回String
* 4. 如果需要參數(shù),接口方法如何定義,當(dāng)前方法如何定義
* 方法內(nèi)部,直接拼接完整的SQL,如果參數(shù)可以通過#{}或${}直接處理,可以設(shè)置占位變量。如果不可以直接處理,則在當(dāng)前方法中設(shè)置。
*/
public String suibian(List
StringBuilder sql = new StringBuilder("insert into tb_dept(dep_id, dep_name, dep_addr) values ");
for (Dept dept : list) {
sql.append("(").append(dept.getDepId()).append(", \"").append(dept.getDepName()).append("\", \"").append(dept.getDepAddr()).append("\"),");
}
// 刪除最后一個逗號
sql.deleteCharAt(sql.length() - 1);
System.out.println("動態(tài)生成的SQL是:" + sql.toString());
return sql.toString();
}
}
package com.bjsxt.test;
import com.bjsxt.mapper.DeptMapper;
import com.bjsxt.pojo.Dept;
import com.bjsxt.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 測試注解
*/
public class TestAnnotation {
// 定義一個實例變量。
private DeptMapper deptMapper;
private SqlSession session;
/**
* 執(zhí)行測試方法前作什么
*/
@Before
public void before() {
session = MyBatisUtils.getFactory().openSession();
deptMapper = session.getMapper(DeptMapper.class);
System.out.println("before方法運(yùn)行,給SqlSession和DeptMapper賦值");
}
@Test
public void testSelectProvider() {
List
System.out.println(list);
list = deptMapper.selectByProvider(1);
System.out.println(list);
list = deptMapper.selectByProvider(10);
System.out.println(list);
list = deptMapper.selectByProvider(11);
System.out.println(list);
}
@Test
public void testInsertBatch() {
List
list.add(new Dept(null, "市場部", "賽蒂工業(yè)園"));
list.add(new Dept(null, "策劃部", "賽蒂工業(yè)園"));
list.add(new Dept(null, "開發(fā)部", "賽蒂工業(yè)園"));
int rows = deptMapper.insertBatch(list);
System.out.println(rows);
session.commit();
session.close();
}
@Test
public void testSelectResults() {
Dept dept = deptMapper.selectByResults(2);
System.out.println(dept);
session.commit();
session.close();
}
@Test
public void testSelect() {
Dept dept = deptMapper.selectById(1);
System.out.println(dept);
System.out.println("================================================");
List
list.forEach(d -> {
System.out.println(d);
});
System.out.println("================================================");
List
maps.forEach(map -> {
System.out.println(map);
});
session.commit();
session.close();
}
@Test
public void testDelete() {
deptMapper.deleteById(5);
session.commit();
session.close();
}
@Test
public void testUpdate() {
Dept dept = new Dept(4, "總裁辦", "賽蒂工業(yè)園");
deptMapper.updateById(dept);
session.commit();
session.close();
}
@Test
public void testInsert() {
SqlSession session = MyBatisUtils.getFactory().openSession();
DeptMapper deptMapper = session.getMapper(DeptMapper.class);
Dept dept = new Dept(null, "測試新增注解", "賽蒂工業(yè)園");
System.out.println("新增前:" + dept);
int rows = deptMapper.insertDept(dept);
System.out.println(rows);
System.out.println("新增后:" + dept);
session.commit();
session.close();
}
}
【19】多表查詢
表格關(guān)系:
1. 一對一
1.1 唯一外鍵: A表格有字段id。 B表格有字段id和a_id,其中a_id是外鍵,引用A表格的id字段,且a_id字段約束是unique。常用
1.2 共享主鍵: A表格有字段id。 B表格有字段id,其中B表格的id字段是主鍵,且是一個外鍵,引用A表格的id字段。不常用
2. 一對多
A表格有字段id。 B表格有字段id和a_id,其中a_id是外鍵,引用A表格的id字段
3. 多對多
A表格有字段id。B表格有字段id。 C表格有字段a_id和b_id,分別引用A表格id和B表格的id。
Java中類型的關(guān)系有:
依賴:弱關(guān)系,有類型A和B,只要A中有B,就是依賴。A依賴B。
聚合:中等強(qiáng)度的關(guān)系,有類型A和B,A中有B類型的或B泛型的實例變量。
組合:強(qiáng)關(guān)系,有類型A和B,A中有B類型或B泛型的實例變量;B中有A類型的或A泛型的實例變量。且A和B不能獨立存在。
MyBatis如何處理多表關(guān)聯(lián)查詢:
當(dāng)前類型引用的其他類型屬性是非集合: 在resultMap標(biāo)簽中,增加子標(biāo)簽
當(dāng)前類型引用的其他類型屬性是集合: 在resultMap標(biāo)簽中,增加子標(biāo)簽
MyBatis實現(xiàn)多表關(guān)聯(lián)數(shù)據(jù)查詢的方式有:
N+1次查詢:如,A類型對應(yīng)表格a,B類型對應(yīng)表格b。A類型中有List類型的屬性。查詢A的同時,要求查詢對應(yīng)的B集合。 1 是查詢A, N 是若干次查詢和A有關(guān)系的B
1次查詢:如,A類型對應(yīng)表格a,B類型對應(yīng)表格b。A類型中有List類型的屬性。查詢A的同時,要求查詢對應(yīng)的B集合。 使用多表聯(lián)合查詢的方式,一次性查詢A和B。
學(xué)習(xí)案例:
1. 查詢?nèi)康刂罚瑫r查詢地址對應(yīng)的客戶數(shù)據(jù)
1.1 N+1次
1.2 1次
2. 查詢?nèi)靠蛻?,同時查詢客戶對應(yīng)的地址集合
2.1 N+1次
2.2 1次
3. 業(yè)務(wù)裝配,就是單獨查詢每個表格的數(shù)據(jù),使用代碼,把數(shù)據(jù)直接的關(guān)系維護(hù)起來。
如:先查詢所有的客戶,迭代分析,根據(jù)客戶主鍵,查詢每個客戶的地址集合,維護(hù)關(guān)系。
create table tb_customer(
id int(11) primary key auto_increment,
name varchar(32) comment '姓名',
username varchar(32) comment '登錄名',
password varchar(32) comment '登錄密碼'
) comment '客戶表格';
create table tb_address(
id int(11) primary key auto_increment,
province varchar(32) comment '省',
city varchar(32) comment '市',
address varchar(255) comment '具體地址',
customer_id int(11) references tb_customer(id)
);
insert into tb_customer(id, name, username, password) values
(default, '張三', 'zhangsan', '123'),
(default, '李四', 'lisi', '123'),
(default, '王五', 'wangwu', '123');
insert into tb_address(id, province, city, address, customer_id) values
(default, '北京', '北京', '西三旗', 1),
(default, '北京', '北京', '西二旗', 1),
(default, '北京', '北京', '西北旺', 1),
(default, '北京', '北京', '經(jīng)海路8號', 2),
(default, '北京', '北京', '經(jīng)海路9號', 2),
(default, '北京', '北京', '經(jīng)海路10號', 2),
(default, '北京', '北京', '天安門東', 3),
(default, '北京', '北京', '天安門西', 3),
(default, '北京', '北京', '前門大街', 3);
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
package com.bjsxt.pojo;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
/**
* 客戶實體
*/
public class Customer implements Serializable {
private Integer id;
private String name;
private String username;
private String password;
private List
addressList;public Customer() {
}
public Customer(Integer id, String name, String username, String password) {
this.id = id;
this.name = name;
this.username = username;
this.password = password;
}
public List
getAddressList() {return addressList;
}
public void setAddressList(List
addressList) {this.addressList = addressList;
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", name='" + name + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Customer customer = (Customer) o;
return Objects.equals(id, customer.id) &&
Objects.equals(name, customer.name) &&
Objects.equals(username, customer.username) &&
Objects.equals(password, customer.password);
}
@Override
public int hashCode() {
return Objects.hash(id, name, username, password);
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
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;
}
}
package com.bjsxt.pojo;
import java.io.Serializable;
import java.util.Objects;
/**
* 地址
*/
public class Address implements Serializable {
private Integer id;
private String province;
private String city;
private String address;
private Customer customer;
private Integer customerId; // 外鍵字段
public Address() {
}
public Address(Integer id, String province, String city, String address) {
this.id = id;
this.province = province;
this.city = city;
this.address = address;
}
@Override
public String toString() {
return "Address{" +
"id=" + id +
", province='" + province + '\'' +
", city='" + city + '\'' +
", address='" + address + '\'' +
'}';
}
public Integer getCustomerId() {
return customerId;
}
public void setCustomerId(Integer customerId) {
this.customerId = customerId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Address address1 = (Address) o;
return Objects.equals(id, address1.id) &&
Objects.equals(province, address1.province) &&
Objects.equals(city, address1.city) &&
Objects.equals(address, address1.address) &&
Objects.equals(customer, address1.customer);
}
@Override
public int hashCode() {
return Objects.hash(id, province, city, address, customer);
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
package com.bjsxt.mapper;
import com.bjsxt.pojo.Address;
import java.util.List;
/**
* 地址數(shù)據(jù)訪問接口
*/
public interface AddressMapper {
List
selectAll();List
selectAll1();}
package com.bjsxt.mapper;
import com.bjsxt.pojo.Customer;
import java.util.List;
/**
* 客戶數(shù)據(jù)訪問接口
*/
public interface CustomerMapper {
List
List
}
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
select addr.id, addr.province, addr.city, addr.address, cus.id as cusId, cus.name, cus.username, cus.password from
tb_address addr left join tb_customer cus on addr.customer_id = cus.id
select="com.bjsxt.mapper.CustomerMapper.selectById" column="{id = customer_id}">
select id, province, city, address, customer_id from tb_address
select id, province, city, address from tb_address where customer_id = #{customerId}
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
ofType="Address">
select cus.id as cusId, cus.name, cus.username, cus.password, addr.id, addr.province, addr.city, addr.address from
tb_customer as cus left join tb_address as addr on cus.id = addr.customer_id
ofType="Address" select="com.bjsxt.mapper.AddressMapper.selectAddrByCustomer" column="{customerId = id}">
select id, name, username, password from tb_customer
select id, name, username, password from tb_customer where id = #{id}
package com.bjsxt.test;
import com.bjsxt.mapper.AddressMapper;
import com.bjsxt.mapper.CustomerMapper;
import com.bjsxt.pojo.Address;
import com.bjsxt.pojo.Customer;
import com.bjsxt.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TestAssociation {
private SqlSession session;
private AddressMapper addressMapper;
private CustomerMapper customerMapper;
@Before
public void before(){
session = MyBatisUtils.getFactory().openSession();
addressMapper = session.getMapper(AddressMapper.class);
customerMapper = session.getMapper(CustomerMapper.class);
}
@Test
public void testAssociation1(){
List
list = addressMapper.selectAll1();for (Address address : list){
System.out.println(address);
System.out.println(address.getCustomer());
System.out.println("====================================================");
}
}
@Test
public void testAssociation(){
List
list = addressMapper.selectAll();// for(Address address : list){
// System.out.println(address);
// //System.out.println(address.getCustomer());
// System.out.println("==========================================================");
// }
}
}
package com.bjsxt.test;
import com.bjsxt.mapper.CustomerMapper;
import com.bjsxt.pojo.Address;
import com.bjsxt.pojo.Customer;
import com.bjsxt.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
public class TestCollection {
private SqlSession session;
private CustomerMapper customerMapper;
@Before
public void before() {
session = MyBatisUtils.getFactory().openSession();
customerMapper = session.getMapper(CustomerMapper.class);
}
@Test
public void testCollection1() {
List
for (Customer customer : list) {
System.out.println(customer);
for (Address address : customer.getAddressList()) {
System.out.println(address);
}
System.out.println("=================================================");
}
}
@Test
public void testCollection() {
List
for (Customer customer : list) {
System.out.println(customer);
for (Address address : customer.getAddressList()) {
System.out.println(address);
}
System.out.println("==============================================================");
}
}
}
【20】延遲加載
延遲加載只能出現(xiàn)在多表聯(lián)合查詢的N+1方式中
表示當(dāng)執(zhí)行當(dāng)前方法時,是否立即執(zhí)行關(guān)聯(lián)方法的SQL
全局配置從3.4.1版本開始需要在MyBatis置文件里面配置lazyLoadingEnabled=true即可在當(dāng)前項目所有N+1的位置開啟延遲加載
局部配置需要在collection或association標(biāo)簽中配置fetchType屬性。fetchType可取值:lazy(延遲加載)和eager(立即加載)
當(dāng)配置了fetchType屬性后,全局settings的配置被覆蓋,對于當(dāng)前標(biāo)簽以fetchType屬性值為準(zhǔn)
javaType="Dept" select="com.bjsxt.mapper.DeptMapper.selectById" column="e_d_id" fetchType="lazy">
select e_id,e_name,e_d_id from emp
屬性名解釋說明可取值默認(rèn)值lazyLoadingEnabled延遲加載的全局開關(guān)。當(dāng)開啟時,所有關(guān)聯(lián)對象都會延遲加載。 特定關(guān)聯(lián)關(guān)系中可通過設(shè)置 fetchType 屬性來覆蓋該項的開關(guān)狀態(tài)。true | falsefalseaggressiveLazyLoading開啟時,任一方法的調(diào)用都會加載該對象的所有延遲加載屬性。 否則,每個延遲加載屬性會按需加載(參考 lazyLoadTriggerMethods)。true | falsefalse (在 3.4.1 及之前的版本中默認(rèn)為 true)
【21】緩存
[1] 一級緩存
會話級緩存、線程級緩存,默認(rèn)開啟一級緩存
要求:必須使用同一個會話、執(zhí)行同一條SQL、使用完全相同的參數(shù)
一級緩存中的數(shù)據(jù)保存流程:
1. 執(zhí)行SQL,查詢數(shù)據(jù)
2. 查詢結(jié)果保存到一級緩存中
[2] 二級緩存
會話工廠緩存、進(jìn)程級緩存。
要求:必須使用同一個會話工廠,執(zhí)行同一條SQL,使用完全相同的參數(shù),且在事務(wù)結(jié)束后,才能使用的緩存。實體必須實現(xiàn)接口Serializable
二級緩存可能保存在內(nèi)存,也可能保存在文件中。MyBatis使用Object輸出/輸入流,實現(xiàn)文件和內(nèi)存的緩存數(shù)據(jù)讀寫。
二級緩存中的數(shù)據(jù)保存流程:
1. 執(zhí)行SQL,查詢數(shù)據(jù)
2. 查詢結(jié)果保存到一級緩存中
3. 提交/回滾/關(guān)閉會話,刷新一級緩存到二級緩存
4. 查詢同樣的SQL,且使用同樣的參數(shù),使用二級緩存
[3] 注意
開發(fā)時,只用一級緩存。不用二級緩存。由于二級緩存,相對性能低,安全差。
二級緩存是以 namespace 為單位的,不同 namespace 下的操作互不影響
查詢數(shù)據(jù)順序 二級-->一級--->數(shù)據(jù)庫--->把數(shù)據(jù)保存到一級,當(dāng)sqlsession關(guān)閉或者提交的時候,把數(shù)據(jù)刷入到二級緩存中
[4] 只讀緩存(readonly=true)
配置的目的是優(yōu)化查詢性能,確保緩存不被寫入,但它不會影響數(shù)據(jù)庫的提交操作。數(shù)據(jù)庫中的數(shù)據(jù)會按照提交操作進(jìn)行實際的修改。
提交操作對數(shù)據(jù)庫的數(shù)據(jù)進(jìn)行實際更改,不管緩存是否配置為只讀。
MyBatis 通過自動清除相關(guān)緩存來確保數(shù)據(jù)的一致性,使得提交操作后的數(shù)據(jù)在下次查詢時能夠得到正確的反映。
/**
* 緩存中存儲的JavaBean對象必須實現(xiàn)序列化接口
*/
public class Dept implements Serializable { ... }
cache標(biāo)簽屬性含義默認(rèn)值type自定義緩存類,要求實現(xiàn)org.apache.ibatis.cache.Cache接口nullreadOnly配置的目的是優(yōu)化查詢性能,確保緩存不被寫入,但它不會影響數(shù)據(jù)庫的提交操作。數(shù)據(jù)庫中的數(shù)據(jù)會按照提交操作進(jìn)行實際的修改。提交操作對數(shù)據(jù)庫的數(shù)據(jù)進(jìn)行實際更改,不管緩存是否配置為只讀。MyBatis 通過自動清除相關(guān)緩存來確保數(shù)據(jù)的一致性,使得提交操作后的數(shù)據(jù)在下次查詢時能夠得到正確的反映。是否只讀true:給所有調(diào)用者返回緩存對象的相同實例。因此這些對象不能被修改。這提供了很重要的性能優(yōu)勢。false:會返回緩存對象的拷貝(通過序列化) 。這會慢一些,但是安全。falseeviction緩存策略LRU(默認(rèn)) – 最近最少使用:移除最長時間不被使用的對象。FIFO – 先進(jìn)先出:按對象進(jìn)入緩存的順序來移除它們。SOFT – 軟引用:基于垃圾回收器狀態(tài)和軟引用規(guī)則移除對象。WEAK – 弱引用:更積極地基于垃圾收集器狀態(tài)和弱引用規(guī)則移除對象。LRUflushInterval刷新間隔,毫秒為單位。默認(rèn)為null,也就是沒有刷新間隔,只有執(zhí)行update、insert、delete語句才會刷新nullsize緩存對象個數(shù)1024blocking是否使用阻塞性緩存BlockingCachetrue:在查詢緩存時鎖住對應(yīng)的Key,如果緩存命中了則會釋放對應(yīng)的鎖,否則會在查詢數(shù)據(jù)庫以后再釋放鎖,保證只有一個線程到數(shù)據(jù)庫中查找指定key對應(yīng)的數(shù)據(jù)false:不使用阻塞性緩存,性能更好false
【23】四大核心接口介紹及執(zhí)行流程
【24】自定義插件
MyBatis中支持?jǐn)U展插件,所有插件都必須實現(xiàn)org.apache.ibatis.plugin.Interceptor接口,可以對四大核心接口進(jìn)行攔截
List
select e_id,e_name,e_d_id from emp
public class MyPageHelper {
protected static Integer pageStart;// 分頁起始行
protected static Integer pageSize;// 查詢條數(shù)
public static void startPage(int pageStartArg,int pageSizeArg){
pageStart = pageStartArg;
pageSize = pageSizeArg;
}
}
package com.bjsxt.interceptor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.sql.Connection;
import java.util.Properties;
// 必須有的注解
/**
* @Intercepts 表示當(dāng)前是一個攔截器。
* @Signature 表示簽名。
* type:攔截器主要攔截的類型.可以是四大核心接口。
* method:攔截type中的哪個方法
* args:method對應(yīng)方法的參數(shù)。這個很重要,因為Java支持方法重載,不設(shè)置參數(shù)可能無法精確到具體的方法
*/
@Intercepts(value = {@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class,Integer.class}
)})
public class MyPageHelperInterceptor implements Interceptor {
// 這個方法的作用:實現(xiàn)攔截業(yè)務(wù)
// 對于自定義分頁插件來說,這個方法的作用就是在后面拼接limit x,y
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 獲取攔截的對象
StatementHandler target = (StatementHandler) invocation.getTarget();
// 獲取SQL綁定器
BoundSql boundSql = target.getBoundSql();
// 獲取SQL語句
String sql = boundSql.getSql();
// 判斷是否已經(jīng)設(shè)置了分頁條件
if(MyPageHelper.pageStart!=null&&MyPageHelper.pageSize!=null) {
// 注意limit前面空格
sql += " limit " +MyPageHelper.pageStart+","+MyPageHelper.pageSize;
}
// 把修改后的SQL重新放回去
MetaObject metaObject = SystemMetaObject.forObject(target);
// 第一個參數(shù)為固定值,表示綁定的SQL
metaObject.setValue("parameterHandler.boundSql.sql",sql);
// 放行繼續(xù)執(zhí)行
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// System.out.println(target.getClass().getName()); 通過輸出可以查詢執(zhí)行此方法時目標(biāo)對象
// 每次調(diào)用四大核心接口都會調(diào)用此方法,只需要對StatementHandler進(jìn)行處理
if(target instanceof StatementHandler){
return Plugin.wrap(target,this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
// 獲取到后面配置插件時的屬性,設(shè)定屬性名為dialect(方言),這個屬性是自定義的。
System.out.println(properties.getProperty("dialect"));
}
}
public class Test {
public static void main(String[] args) throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis.cfg.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = factory.openSession();
EmpMapper empMapper = session.getMapper(EmpMapper.class);
// 設(shè)置分頁條件代碼必須放在調(diào)用SQL上面
MyPageHelper.startPage(0,2);
List
System.out.println(list);
session.close();
}
}
【25】執(zhí)行器類型
【26】執(zhí)行原理
柚子快報激活碼778899分享:尚硅谷MyBatis 基礎(chǔ)筆記
好文推薦
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。