MyBatis入門到精通:CRUD實戰指南

1. MyBatis

ORM:對象關系映射

  • O(Object):Java虛擬機中的Java對象

  • R(Relational):關系型數據庫

  • M(Mapping):將Java虛擬機中的Java對象映射到數據庫表中一行記錄,或是將數據庫表中一行記錄映射成Java虛擬機中的一個Java對象。

1.1 MyBatis入門程序

CarMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace先隨意寫一個-->
<mapper namespace="car"><!--insert sql:保存一個汽車信息--><insert id="insertCar">insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,'102','豐田mirai',40.30,'2014-10-05','氫能源')</insert>
</mapper>

測試程序

public class MyBatisIntroductionTest {public static void main(String[] args) {// 1. 創建SqlSessionFactoryBuilder對象SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();// 2. 創建SqlSessionFactory對象InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);// SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));// 3. 創建SqlSession對象SqlSession sqlSession = sqlSessionFactory.openSession();// 4. 執行sqlint count = sqlSession.insert("insertCar"); // 這個"insertCar"必須是sql的idSystem.out.println("插入幾條數據:" + count);// 5. 提交(mybatis默認采用的事務管理器是JDBC,默認是不提交的,需要手動提交。)sqlSession.commit();// 6. 關閉資源(只關閉是不會提交的)sqlSession.close();}
}

1.2 工具類SqlSessionUtil

public class SqlSessionUtil {// 工具類的構造方法一般都是私有化的// 工具類中所有的方法都是靜態的,直接采用類名即可調用,不需要new對象// 為了防止new對象,構造方法私有化private SqlSessionUtil(){}private static SqlSessionFactory sqlSessionFactory;// 類加載時執行// SqlSessionUtil工具類在進行第一次加載的時候,解析mybatis-config.xml文件,創建SqlSessionFactory對象static {try {sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));} catch (IOException e) {throw new RuntimeException(e);}}/*** 獲取會話對象* @return*/public static SqlSession openSession() {return sqlSessionFactory.openSession();}
}

2. MyBatis完成CRUD

<insert id="insertCarByPOJO"><!--#{} 里寫的是POJO的屬性名-->insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>

2.1 Map傳參

public class CarMapperTest {@Testpublic void testInsertCar(){// 準備數據Map<String, Object> map = new HashMap<>();// 讓key的可讀性增強map.put("carNum", "103");map.put("brand", "奔馳E300L");map.put("guidePrice", 50.3);map.put("produceTime", "2020-10-01");map.put("carType", "燃油車");// 獲取SqlSession對象SqlSession sqlSession = SqlSessionUtil.openSession();// 執行SQL語句(使用map集合給sql語句傳遞數據)int count = sqlSession.insert("insertCar", map);System.out.println("插入了幾條記錄:" + count);}
}

2.2 POJO傳參

@Test
public void testInsertCarByPOJO(){// 創建POJO,封裝數據Car car = new Car();car.setCarNum("103");car.setBrand("奔馳C200");car.setGuidePrice(33.23);car.setProduceTime("2020-10-11");car.setCarType("燃油車");// 獲取SqlSession對象SqlSession sqlSession = SqlSessionUtil.openSession();// 執行SQL,傳數據int count = sqlSession.insert("insertCarByPOJO", car);System.out.println("插入了幾條記錄" + count);
}

如果采用map集合傳參,#{} 里寫的是map集合的key,如果key不存在不會報錯,數據庫表中會插入NULL。

如果采用POJO傳參,#{} 里寫的是get方法的方法名去掉get之后將剩下的單詞首字母變小寫(例如:getAge對應的是#{age},getUserName對應的是#{userName}),如果這樣的get方法不存在會報錯。

2.3 update

<update id="updateCarByPOJO">update t_car set car_num = #{carNum}, brand = #{brand}, guide_price = #{guidePrice}, produce_time = #{produceTime}, car_type = #{carType} where id = #{id}
</update>

2.4 Select

resultType? 告訴mybatis返回一個什么類型的Java對象

<select id="selectCarById" resultType="com.powernode.mybatis.pojo.Car">select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where id = #{id}
</select><select id="selectCarAll" resultType="com.powernode.mybatis.pojo.Car"><!--記得使用as起別名,讓查詢結果的字段名和java類的屬性名對應上。-->selectid, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carTypefromt_car
</select>

2.4.1 查詢一條語句

@Test
public void testSelectCarById(){// 獲取SqlSession對象SqlSession sqlSession = SqlSessionUtil.openSession();// 執行SQL語句Object car = sqlSession.selectOne("selectCarById", 1);System.out.println(car);
}

2.4.2 查詢多條語句

@Test
public void testSelectCarAll(){// 獲取SqlSession對象SqlSession sqlSession = SqlSessionUtil.openSession();// 執行SQL語句List<Object> cars = sqlSession.selectList("selectCarAll");// 輸出結果cars.forEach(car -> System.out.println(car));
}

4. MyBatis核心配置文件

<?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><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/powernode"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><mappers><mapper resource="CarMapper.xml"/><mapper resource="CarMapper2.xml"/></mappers>
</configuration>

1. configuration:根標簽,表示配置信息。

2. environments:環境(多個),以“s”結尾表示復數,也就是說mybatis的環境可以配置多個數據源。

? ? ? default屬性:表示默認使用的是哪個環境,default后面填寫的是environment的id。default的值只需要和environment的id值一致即可

3. environment:具體的環境配置(主要包括:事務管理器的配置 + 數據源的配置

? ? ? id:給當前環境一個唯一標識,該標識用在environments的default后面,用來指定默認環境的選擇。

4. transactionManager:配置事務管理器

? ? ?type屬性:指定事務管理器具體使用什么方式,可選值包括兩個

? ? JDBC:使用JDBC原生的事務管理機制。底層原理:事務開啟conn.setAutoCommit(false); ...處理業務...事務提交conn.commit();

? ? ?MANAGED:交給其它容器來管理事務,比如WebLogic、JBOSS等。如果沒有管理事務的容器,則沒有事務。沒有事務的含義:只要執行一條DML語句,則提交一次

5. dataSource:指定數據源

? ? ? type屬性:用來指定具體使用的數據庫連接池的策略,可選值包括三個

? ? ? 5.1 UNPOOLED:采用傳統的獲取連接的方式,雖然也實現Javax.sql.DataSource接口,但是并沒有使用池的思想。

? ? ? ? ? ? driver 這是 JDBC 驅動的 Java 類全限定名。url 這是數據庫的 JDBC URL 地址。

? ? ? ? ? ? username 登錄數據庫的用戶名。password 登錄數據庫的密碼。

? ? ? ? ? ? defaultTransactionIsolationLevel 默認的連接事務隔離級別。

? ? ? ? ? ? defaultNetworkTimeout 等待數據庫操作完成的默認網絡超時時間(單位:毫秒)

? ? ? 5.2 POOLED:采用傳統的javax.sql.DataSource規范中的連接池,mybatis中有針對規范的實現。

? ? ? ? ? ? property可以是(除了包含UNPOOLED中之外):

? ? ? ? ? ? poolMaximumActiveConnections 在任意時間可存在的活動(正在使用)連接數量,默認值:10

? ? ? ? ? ? poolMaximumIdleConnections 任意時間可能存在的空閑連接數。

? ? ? 5.3 JNDI:采用服務器提供的JNDI技術實現,來獲取DataSource對象,不同的服務器所能拿到DataSource是不一樣。如果不是web或者maven的war工程,JNDI是不能使用的。

? ? ?initial_context 這個屬性用來在 InitialContext 中尋找上下文(即,initialContext.lookup(initial_context))這是個可選屬性,如果忽略,那么將會直接從 InitialContext 中尋找 data_source 屬性。

? ? ? ? ? ? data_source 這是引用數據源實例位置的上下文路徑。提供了 initial_context 配置時會在其返回的上下文中進行查找,沒有提供時則直接在 InitialContext 中查找。

6. mappers:在mappers標簽中可以配置多個sql映射文件的路徑。

7. mapper:配置某個sql映射文件的路徑

? ? ? resource屬性:使用相對于類路徑的資源引用方式;

? ? ? url屬性:使用完全限定資源定位符(URL)方式;

4.1 environment

<!--默認使用開發環境-->
<!--<environments default="dev">-->
<!--默認使用生產環境-->
<environments default="production">
// 一個數據庫對應一個SqlSessionFactory對象
// 兩個數據庫對應兩個SqlSessionFactory對象,以此類推
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();// 使用默認數據庫
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession(true);
int count = sqlSession.insert("insertCar", car);System.out.println("插入了幾條記錄:" + count);
// 使用指定數據庫
SqlSessionFactory sqlSessionFactory1 = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "dev");
SqlSession sqlSession1 = sqlSessionFactory1.openSession(true);
int count1 = sqlSession1.insert("insertCar", car);
System.out.println("插入了幾條記錄:" + count1);

4.2 transactionManager

<!--transactionManager標簽:1.作用:配置事務管理器。指定mybatis具體使用什么方式去管理事務2.type屬性有兩個值:第一個:JDBC:使用原生的JDBC代碼來管理事務。conn.setAutoCommit(false);....conn.commit();第二個:MANAGED:mybatis不再負責事務的管理,將事務管理交給其它的JEE(JavaEE)容器來管理。例如:spring3. 不區分大小寫,但是只能使用這兩個值4. 在mybatis中提供了一個事務管理器接口:Transaction該接口下有兩個實現類:JdbcTransactionManagedTransaction如果type="JDBC",那么底層會實例化JdbcTransaction對象如果type="MANAGED",那么底層會實例化ManagedTransaction
-->
<transactionManager type="JDBC"/>

事務管理器是:JDBC

采用JDBC的原生事務機制:

  • 開啟事務:conn.setAutoCommit(false);

  • 處理業務......

  • 提交事務:conn.commit();

事務管理器是:MANAGED

交給容器去管理事務,但目前使用的是本地程序,沒有容器的支持,當mybatis找不到容器的支持時:沒有事務。也就是說只要執行一條DML語句,則提交一次。

4.3 dataSource

<!--dataSource配置:1.dataSource被稱為數據源2.dataSource作用是什么?為程序提供Connection對象。(但凡是給程序提供Connection對象的,都叫做數據源。)3.數據源實際上是一套規范。JDK中有這套規范:javax.sql.DataSource(這個數據源的規范,這套接口實際上是JDK規定的。)4.我們自己也可以編寫數據源組件,只要實現javax.sql.Datasource接口就行了。實現接口當中所有的方法。這樣就有了自己的數據源。比如你可以寫一個屬于自己的數據庫連接池(數據庫連接池是提供連接對象的,所以數據庫連接池就是一個數據源)。5.常見的數據源組件有哪些呢【常見的數據庫連接池有哪些呢】?阿里巴巴的德魯伊連接池:druidc3p0dbcp..6.type屬性用來指定數據源的類型,就是指定具體使用什么方式來獲取Connection對象:type屬性有三個值:必須是三選一。type="[UNPOOLED|POOLED|JNDI]"UNPOOLED:不使用數據庫連接池技術。每一次請求過來之后,都是創建新的Connection對象。PO0LED:使用mybatis自已實現的數據庫連接池。JNDI:集成其它第三方的數據庫連接池。(規范)如果想使用dbcp、c3p0、druid(德魯伊)等,需要使用這種方式。大部分的web容器(Tomcat、Jetty、WebLogic、WebSphere)也都實現了  JNDI規范JNDI是:java命名目錄接口
-->
<dataSource type="POOLED">

4.4 properties

<dataSource type="POOLED"><!--采用的是property標簽的name屬性--><property name="driver" value="${jdbc.driver}"/><property name="url" value="jdbc:mysql://localhost:3306/powernodemb"/><property name="username" value="root"/><property name="password" value="1234"/><!--提醒:正常使用連接池的話,池中有很多參數是需要設置的。設置好參數,可以讓連接池發揮的更好。事半功倍的效果。--><!--具體連接池當中的參數如何配置呢?需要反復的根據當前業務情況進行測試。--><!--poolMaximumActiveconnections:連接池當中最多的正在使用的連接對象的數量上限。最多有多少個連接可以活動。默認值10--><property name="poolMaximumActiveConnections" value="3"/><!--每隔2秒打印日志,并且嘗試獲取連接對象--><property name="poolTimeToWait" value="2000"/><!--強行讓某個連接空閑,超時時間的設置--><property name="poolMaximumCheckoutTime" value="10000"/><!--最多的空閑數量--><property name="poolMaximumIdleConnections" value="5"/>
</dataSource>

4.5 mapper

<mappers><!--sql映射文件(Mapper文件)創建好之后,需要將該文件路徑配置到這里--><!--resource屬性自動會從類的根路徑下開始查找資源--><mapper resource="CarMapper.xml"/>
</mappers>

5. 在Web中應用MyBatis

5.1 MyBatis對象作用域

SqlSessionFactoryBuilder

這個類可以被實例化、使用和丟棄,一旦創建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 實例的最佳作用域是方法作用域(也就是局部方法變量)。 你可以重用 SqlSessionFactoryBuilder 來創建多個 SqlSessionFactory 實例,但最好還是不要一直保留著它,以保證所有的 XML 解析資源可以被釋放給更重要的事情。

SqlSessionFactory

SqlSessionFactory 一旦被創建就應該在應用的運行期間一直存在,沒有任何理由丟棄它或重新創建另一個實例。 使用 SqlSessionFactory 的最佳實踐是在應用運行期間不要重復創建多次,多次重建 SqlSessionFactory 被視為一種代碼“壞習慣”。因此 SqlSessionFactory 的最佳作用域是應用作用域。 有很多方法可以做到,最簡單的就是使用單例模式或者靜態單例模式。

SqlSession

每個線程都應該有它自己的 SqlSession 實例。SqlSession 的實例不是線程安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。 絕對不能將 SqlSession 實例的引用放在一個類的靜態域,甚至一個類的實例變量也不行。 也絕不能將 SqlSession 實例的引用放在任何類型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你現在正在使用一種 Web 框架,考慮將 SqlSession 放在一個和 HTTP 請求相似的作用域中。 換句話說,每次收到 HTTP 請求,就可以打開一個 SqlSession,返回一個響應后,就關閉它。 這個關閉操作很重要,為了確保每次都能執行關閉操作,你應該把這個關閉操作放到 finally 塊中。

為了保證service和dao中使用的SqlSession對象是同一個,可以將SqlSession對象存放到ThreadLocal當中。修改SqlSessionUtil工具類:

5.2 SqlSessionUtil工具類

public class SqlSessionUtil {// 工具類的構造方法一般都是私有化的// 工具類中所有的方法都是靜態的,直接采用類名即可調用,不需要new對象// 為了防止new對象,構造方法私有化private SqlSessionUtil(){}private static SqlSessionFactory sqlSessionFactory;// 類加載時執行// SqlSessionUtil工具類在進行第一次加載的時候,解析mybatis-config.xml文件,創建SqlSessionFactory對象static {try {sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));} catch (IOException e) {throw new RuntimeException(e);}}// 全局的,服務器級別的,一個服務器當中定義一個即可// 將SqlSession對象放到ThreadLocal當中,為了保證一個線程對應一個SqlSessionprivate static ThreadLocal<SqlSession> local = new ThreadLocal<>();/*** 獲取會話對象,該會話支持自動提交。* @return*/public static SqlSession openSession() {
//		return sqlSessionFactory.openSession();SqlSession sqlSession = local.get();if(sqlSession == null) {sqlSession = sqlSessionFactory.openSession();// 將sqlSession對象綁定到當前線程上local.set(sqlSession);}return sqlSession;}/*** 關閉SqlSession對象(從當前線程中移除SqlSession對象)* @param sqlSession*/public static void close(SqlSession sqlSession) {if(sqlSession != null) {sqlSession.close();// 移除SqlSession對象和當前線程的綁定關系// 因為tomcat服務器支持線程池,用過的線程對象t1,下一次還會使用local.remove();}}
}

5.3 javassist生成實現類

public class GenerateDaoByJavassist {/*** 根據dao接口生成dao接口的代理對象** @param sqlSession   sql會話* @param daoInterface dao接口* @return dao接口代理對象*/public static Object getMapper(SqlSession sqlSession, Class daoInterface) {ClassPool pool = ClassPool.getDefault();// 生成代理類CtClass ctClass = pool.makeClass(daoInterface.getPackageName() + ".impl." + daoInterface.getSimpleName() + "Impl");// 接口CtClass ctInterface = pool.makeClass(daoInterface.getName());// 代理類實現接口ctClass.addInterface(ctInterface);// 獲取所有的方法Method[] methods = daoInterface.getDeclaredMethods();Arrays.stream(methods).forEach(method -> {// 拼接方法的簽名StringBuilder methodStr = new StringBuilder();String returnTypeName = method.getReturnType().getName();methodStr.append(returnTypeName);methodStr.append(" ");String methodName = method.getName();methodStr.append(methodName);methodStr.append("(");Class<?>[] parameterTypes = method.getParameterTypes();for (int i = 0; i < parameterTypes.length; i++) {methodStr.append(parameterTypes[i].getName());methodStr.append(" arg");methodStr.append(i);if (i != parameterTypes.length - 1) {methodStr.append(",");}}methodStr.append("){");// 方法體當中的代碼怎么寫?// 獲取sqlId(這里非常重要:因為這行代碼導致以后namespace必須是接口的全限定接口名,sqlId必須是接口中方法的方法名。)String sqlId = daoInterface.getName() + "." + methodName;// 獲取SqlCommondTypeString sqlCommondTypeName = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType().name();if ("SELECT".equals(sqlCommondTypeName)) {methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");methodStr.append("Object obj = sqlSession.selectOne(\"" + sqlId + "\", arg0);");methodStr.append("return (" + returnTypeName + ")obj;");} else if ("UPDATE".equals(sqlCommondTypeName)) {methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");methodStr.append("int count = sqlSession.update(\"" + sqlId + "\", arg0);");methodStr.append("return count;");}methodStr.append("}");System.out.println(methodStr);try {// 創建CtMethod對象CtMethod ctMethod = CtMethod.make(methodStr.toString(), ctClass);ctMethod.setModifiers(Modifier.PUBLIC);// 將方法添加到類ctClass.addMethod(ctMethod);} catch (CannotCompileException e) {throw new RuntimeException(e);}});try {// 創建代理對象Class<?> aClass = ctClass.toClass();Constructor<?> defaultCon = aClass.getDeclaredConstructor();Object o = defaultCon.newInstance();return o;} catch (Exception e) {throw new RuntimeException(e);}}
}

6. MyBatis接口代理機制

@Test
public void testInsert(){SqlSession sqlSession = SqlSessionUtil.openSession();// 面向接口獲取接口的代理對象CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(null, "8654", "小米yu7", 35.09, "2020-10-10", "新能源");int count = mapper.insert(car);System.out.println(count);sqlSession.commit();
}

AccountMapper.xml文件中的namespace必須和dao接口的全限定名稱一致,id必須和dao接口中方法名一致。

7. MyBatis的小技巧

#{}:先編譯sql語句,再給占位符傳值,底層是PreparedStatement實現。可以防止sql注入,比較常用。

${}:先進行sql語句拼接,然后再編譯sql語句,底層是Statement實現。存在sql注入現象。只有在需要進行sql語句關鍵字拼接的情況下才會用到。 字符串,在sql語句中應該添加單引號,所以不適合使用${}。

<mapper namespace="com.powernode.mybatis.mapper.CarMapper"><select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwhere<!--car_type = #{carType}--><!--car_type = ${carType}-->car_type = '${carType}'</select>
</mapper>

7.1 必須使用${}

當需要進行sql語句關鍵字拼接的時候。必須使用${}

需求:通過向sql語句中注入asc或desc關鍵字,來完成數據的升序或降序排列。

<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_car<!--order by carNum #{key}-->order by carNum ${key}
</select>
@Test
public void testSelectAll(){CarMapper mapper = (CarMapper) SqlSessionUtil.openSession().getMapper(CarMapper.class);List<Car> cars = mapper.selectAll("desc");cars.forEach(car -> System.out.println(car));
}

拼接表名到sql語句當中應該使用#{} 還是 ${} 呢?

使用#{}會是這樣:select * from 't_car'

使用${}會是這樣:select * from t_car


/*** 根據id批量刪除* @param ids* @return*/
int deleteBatch(String ids);
<delete id="deleteBatch">delete from t_car where id in(${ids})
</delete>
@Test
public void testDeleteBatch(){CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);int count = mapper.deleteBatch("1,2,3");System.out.println("刪除了幾條記錄:" + count);SqlSessionUtil.openSession().commit();
}

模糊查詢

<select id="selectLikeByBrand" resultType="Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherebrand like concat('%',#{brand},'%')
</select>
/*** 根據品牌進行模糊查詢* @param likeBrank* @return*/
List<Car> selectLikeByBrand(String likeBrank);@Test
public void testSelectLikeByBrand(){CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);List<Car> cars = mapper.selectLikeByBrand("奔馳");cars.forEach(car -> System.out.println(car));
}第一種方案:'%${brand}%'
第二種方案:concat函數,這個是mysql數據庫當中的一個函數,專門進行字符串拼接concat('%',#{brand},'%')
第三種方案:比較雞肋了。可以不算。concat('%','${brand}','%')
第四種方案:"%"#{brand}"%"

7.2 typeAliases 起別名

<properties resource="jdbc.properties"/><!--起別名的標簽-->
<typeAliases><typeAlias type="com.powernode.pojo.Car" alias="aaa"/><typeAlias type="com.powernode.pojo.Log" alias="bbb"/><!--采用默認的別名機制, 別名就是類的簡名:com.powernode.pojo.Car指的就是Car--><!--alias是大小寫不敏感的。也就是說假設alias="Car",再用的時候,可以CAR,也可以car,也可以Car--><typeAlias type="com.powernode.pojo.Car"/><typeAlias type="com.powernode.pojo.Log"/><!--包下所有的類自動起別名。使用簡名作為別名。--><package name="com.powernode.pojo"/>
</typeAliases>

7.3 mappers

SQL映射文件的配置方式包括四種:

  • resource:從類路徑中加載

  • url:從指定的全限定資源路徑中加載

  • class:使用映射器接口實現類的完全限定類名

  • package:將包內的映射器接口實現全部注冊為映射器

<mappers><mapper resource="org/mybatis/builder/AuthorMapper.xml"/><mapper resource="org/mybatis/builder/BlogMapper.xml"/><mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<mapper resource="CarMapper.xml"/> 要求類的根路徑下必須有:CarMapper.xml
<mapper url="file:///d:/CarMapper.xml"/> 要求在d:/下有CarMapper.xml文件
<mapper class="全限定接口名,帶有包名"/>
<mappers><!-- <mapper resource="CarMapper.xml"/>--><!-- <mapper resource="LogMapper.xml"/>--><package name="com.hnlg.mapper"/>
</mappers>
mapper標簽的屬性可以有三個:resource:這種方式是從類的根路徑下開始查找資源。采用這種方式的話,配置文件需要放到類路徑當中才行。url: 絕對路徑的方式,這種方式不要求配置文件必須放到類路徑當中,哪里都行,只要提供一個絕對路徑就行。這種方式使用極少,因為移植性太差。class: 這個位置提供的是mapper接口的全限定接口名,必須帶有包名的。思考:mapper標簽的作用是指定SqlMapper.xml文件的路徑,指定接口名有什么用呢?<mapper class="com.powernode.mapper.CarMapper"/>如果你class指定是:com.powernode.mapper.CarMapper那么mybatis框架會自動去com/powernode/mapper目錄下查找CarMapper.xml文件。注意:也就是說:如果你采用這種方式,那么你必須保證CarMapper.xml文件和CarMapper接口必須在同一個目錄下。并且名字一致。CarMapper接口-> CarMapper.xmlLogMapper接口-> LogMapper.xml....<package name="com.hnlg.mapper"/>   最常用的提醒!!!!!!!!!!!!!!!!!!!!!!!在IDEA的resources目錄下新建多重目錄的話,必須是這樣創建:com/powernode/mybatis/mapper不能這樣:com.powernode.mybatis.mapper

7.4 插入數據獲取自動生成主鍵

<!--1. useGeneratedKeys="true"使用自動生成的主鍵值2. keyProperty="id"指定主鍵值賦值給對象的哪個屬性。
-->
<insert id="insertCarUseGneratedKeys" useGeneratedKeys="true" keyProperty="id">insert into t_car values(null, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})
</insert>

8. MyBatis參數處理

8.1 單個簡單類型參數

/*** 根據birth查詢* @param birth* @return*/
List<Student> selectByBirth(Date birth);/*** 根據sex查詢* @param sex* @return*/
List<Student> selectBySex(Character sex);
<select id="selectByBirth" resultType="Student">select * from t_student where birth = #{birth}
</select><select id="selectBySex" resultType="Student">select * from t_student where sex = #{sex}
</select>
@Test
public void testSelectByBirth() throws ParseException {SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");Date birth = sdf.parse("2005-09-06");List<Student> students = mapper.selectByBirth(birth);students.forEach(System.out::println);sqlSession.close();
}@Test
public void testSelectBySex(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);/*char類型對應的包裝類*/Character sex = Character.valueOf('男');List<Student> students = mapper.selectBySex(sex);students.forEach(System.out::println);sqlSession.close();
}

8.2 Map類型參數

/*** 根據name和age查詢* 手動封裝Map集合,將每個條件以key和value的形式存放到集合中。然后在使用的時候通過#{map集合的key}來取值。* @param paramMap* @return*/
List<Student> selectByParamMap(Map<String,Object> paramMap);
<select id="selectByParamMap" resultType="Student">select * from t_student where name = #{nameKey} and age = #{ageKey}
</select>
@Test
public void testSelectByParamMap(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);// 準備MapMap<String,Object> paramMap = new HashMap<>();paramMap.put("nameKey", "張三");paramMap.put("ageKey", 20);List<Student> students = mapper.selectByParamMap(paramMap);students.forEach(System.out::println);sqlSession.close();
}

8.3 多參數

/*** 根據name和sex查詢Student信息、* 如果是多個參數的話,mybatis框架底層是怎么做的呢?* 	mybatis框架會自動創建一個ap集合。并且Map集合是以這種方式存儲參數的:* 		map.put("arg0",name);* 		map.put("arg1",sex);* 		map.put("param1", name);* 		map.put("param2", sex);* @param name* @param sex* @return*/
List<Student> selectByNameAndSex(String name, Character sex);
<select id="selectByNameAndSex" resultType="Student">/*select * from t_student where name = #{name} and sex = #{sex}  代碼錯誤*//*select * from t_student where name = #{arg0} and sex = #{arg1}*/select * from t_student where name = #{arg0} and sex = #{param1}
</select>
@Test
public void testSelectByNameAndSex(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);List<Student> students = mapper.selectByNameAndSex("張三", '男');students.forEach(System.out::println);sqlSession.close();
}

8.3.1 @Param注解

/*** Param注解底層* 	map.put("name",name);* 	map.put("sex",sex);** @param name* @param sex* @return*/
List<Student> selectByNameAndSex2(@Param("name") String name, @Param("sex") Character sex);
<!--arg0和arg1失效了,但是param0和param1還有效;但是主要還是使用@Param注解中的內容-->
<select id="selectByNameAndSex2" resultType="student">select * from t_student where name = #{name} and sex = #{sex}
</select>

9. MyBatis查詢語句專題

9.1 返回Map

/*** 根據id查詢返回map集合對象* @param id* @return*/
Map<String, Object> selectByIdReturnMap(Long id);@Test
public void testSelectByIdReturnMap() {SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper carMapper = sqlSession.getMapper(CarMapper.class);Map<String, Object> car = carMapper.selectByIdReturnMap(1L);System.out.println(car);sqlSession.close();
}
<select id="selectByIdReturnMap" resultType="Map">select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car where id = #{id}
</select>

9.2 返回list<Map>

/*** 查詢所有的Car,返回一個List集合。List集合中存儲的是Map集合。* @return*/
List<Map<String,Object>> selectAllRetListMap();@Test
public void testSelectAllRetListMap(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List<Map<String,Object>> cars = mapper.selectAllRetListMap();cars.forEach(System.out::println);sqlSession.close();
}
<select id="selectAllRetListMap" resultType="map">select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
</select>

9.3 返回Map<String, Map>

/*** 獲取所有的Car,返回一個Map集合。* Map集合的key是Car的id。* Map集合的value是對應Car。* @return*/
@MapKey("id")	// 將查詢結果的id值作為整個大Map集合的key
Map<Long,Map<String,Object>> selectAllRetMap();@Test
public void testSelectAllRetMap() {SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Map<Long, Map<String,Object>> cars = mapper.selectAllRetMap();System.out.println(cars);sqlSession.close();
}
<select id="selectAllRetMap" resultType="map">select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
</select>

9.4 resultMap結果映射

查詢結果的列名和java對象的屬性名對應不上怎么辦?

  • 第一種方式:as 給列起別名

  • 第二種方式:使用resultMap進行結果映射

  • 第三種方式:是否開啟駝峰命名自動映射(配置settings)

<!--1. 不再起別名了,起一個結果映射,在這個結果映射當中指定數據庫表的字段名和java類的屬性名的對應關系2. type屬性:用來指定POJO類的類名3. id屬性:指定resultMap的唯一標識,這個id要在select標簽中使用type="com.hnlg.pojo.Car" 因為<typeAliases><package name="com.hnlg.pojo"/></typeAliases>  所以為Car
-->
<resultMap id="CarResultMap" type="Car"><!--如果數據庫表中有主鍵,建議配置一個id標簽--><id property="id" column="id"/><!--property填寫POJO類的屬性名--><!--column填寫數據庫表的字段名--><result property="carNum" column="car_num"/><!--當屬性名和數據庫列名一致時,可以省略。但建議都寫上。--><!--javaType用來指定屬性類型。jdbcType用來指定列類型。一般可以省略。--><result property="brand" column="brand" javaType="string" jdbcType="VARCHAR"/><result property="guidePrice" column="guide_price"/><result property="produceTime" column="produce_time"/><result property="carType" column="car_type"/>
</resultMap>
<!--select標簽的resultMap屬性,用來指定使用哪個結果映射。resultMap后面的值是resultMap的id-->
<select id="selectAllByResultMap" resultMap="CarResultMap">select * from t_car
</select>

9.5 駝峰命名自動映射

使用這種方式的前提是:屬性名遵循Java的命名規范,數據庫表的列名遵循SQL的命名規范。

Java命名規范:首字母小寫,后面每個單詞首字母大寫,遵循駝峰命名方式。

SQL命名規范:全部小寫,單詞之間采用下劃線分割。

<!--放在properties標簽后面-->
<settings><setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!--開啟自動駝峰映射-->
<!--不開啟的話很多值會出現null值-->
<select id="selectAllByMapUnderscoreToCamelCase" resultType="Car">select * from t_car
</select>
/*** 查詢所有Car,啟用駝峰命名自動映射* @return*/
List<Car> selectAllByMapUnderscoreToCamelCase();@Test
public void testSelectAllByMapUnderscoreToCamelCase(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List<Car> cars = mapper.selectAllByMapUnderscoreToCamelCase();cars.forEach(System.out::println);sqlSession.close();
}

9.6 返回總記錄條數

<select id="selectTotal" resultType="long">/* 可以是count(*)也可以是count(1),但是如果count(某個字段)會自動去除null值 */select count(*) from t_car
</select>

10. 動態SQL

10.1 if標簽

/*** 多條件查詢Car* 	多參數查詢時需要使用到 @Param 注解* @param brand* @param guidePrice* @param carType* @return*/
List<Car> selectByMultiCondition(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
<select id="selectByMultiCondition" resultType="Car">select * from t_car where 0 = 0<!--1.if標簽中test屬性是必須的。2.if標簽中test屬性的值是false或者true。3.如果test是true,則if標簽中的sql語句就會拼接。反之,則不會拼接。4.test屬性中可以使用的是:當使用了@Param注解,那么test中要出現的是@Param注解指定的參數名。@Param("brand"),那么這里只能使用brand當沒有使用@Param注解,那么test中要出現的是:param1 param2 param3 arg0 arg1 arg2....當使用了P0J0,那么test中出現的是P0J0類的屬性名。5.在mybatis的動態SQL當中,不能使用&&,只能使用and。--><if test="brand != null and brand != ''">and brand like "%"#{brand}"%"</if><if test="guidePrice != null and guidePrice != ''">and guide_price >= #{guidePrice}</if><if test="carType != null and carType != ''">and car_type = #{carType}</if>
</select>

10.2 where標簽

where標簽的作用:讓where子句更加動態智能。

  • 所有條件都為空時,where標簽保證不會生成where子句。

  • 自動去除某些條件前面多余的and或or。

  • 如果最后一個條件為空,那么查詢語句最后會多出一個 and

<select id="selectByMultiConditionWithWhere" resultType="car">select * from t_car<where><if test="brand != null and brand != ''">and brand like #{brand}"%"</if><if test="guidePrice != null and guidePrice != ''">and guide_price >= #{guidePrice}</if><if test="carType != null and carType != ''">and car_type = #{carType}</if></where>
</select>

10.3 trim標簽

  • 所有條件都為空時,不會生成where子句。

  • 如果最后一個條件為空,查詢語句最后不會會多出一個 and

<select id="selectByMultiConditionWithTrim" resultType="car">select * from t_car<!--- prefix:在trim標簽中的語句前  添加  內容- suffix:在trim標簽中的語句后  添加  內容- prefixOverrides:前綴  覆蓋掉(去掉)- suffixOverrides:后綴  覆蓋掉(去掉)--><trim prefix="where" suffixOverrides="and|or"><if test="brand != null and brand != ''">brand like "%"#{brand}"%" and</if><if test="guidePrice != null and guidePrice != ''">guide_price >= #{guidePrice} and</if><if test="carType != null and carType != ''">car_type = #{carType}</if></trim>
</select>

10.4 set標簽

使用在update語句當中,用來生成set關鍵字,同時去掉最后多余的“,”

/*** 更新Car   Set標簽* @param car* @return*/
int updateWithSet(Car car);@Test
public void testUpdateWithSet(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(10L,"1001","小米su7",10.0,"",null);mapper.updateWithSet(car);sqlSession.commit();sqlSession.close();
}
<update id="updateWithSet">update t_car<set><if test="carNum != null and carNum != ''">car_num = #{carNum},</if><if test="brand != null and brand != ''">brand = #{brand},</if><if test="guidePrice != null and guidePrice != ''">guide_price = #{guidePrice},</if><if test="produceTime != null and produceTime != ''">produce_time = #{produceTime},</if><if test="carType != null and carType != ''">car_type = #{carType},</if></set>where id = #{id}
</update>

10.5 choose when otherwise

需求:先根據品牌查詢,如果沒有提供品牌,再根據指導價格查詢,如果沒有提供指導價格,就根據生產日期查詢。

<!--需求:先根據品牌查詢,如果沒有提供品牌,再根據指導價格查詢,如果沒有提供指導價格,就根據生產日期查詢。-->
<select id="selectWithChoose" resultType="car">select * from t_car<where><choose><when test="brand != null and brand != ''">brand like "%"#{brand}"%"</when><when test="guidePrice != null and guidePrice != ''">guide_price >= #{guidePrice}</when><otherwise>produce_time >= #{produceTime}</otherwise></choose></where>
</select>
/*** 使用choose when otherwise標簽查詢* 	需求:先根據品牌查詢,如果沒有提供品牌,再根據指導價格查詢,如果沒有提供指導價格,就根據生產日期查詢。* @param brand* @param guidePrice* @param produceTime* @return*/
List<Car> selectWithChoose(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("produceTime") String produceTime);@Test
public void testSelectWithChoose(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);//List<Car> cars = mapper.selectWithChoose("豐田霸道", 20.0, "2000-10-10");//List<Car> cars = mapper.selectWithChoose("", 20.0, "2000-10-10");//List<Car> cars = mapper.selectWithChoose("", null, "2000-10-10");// 全為空,則會按照最后一個查,但是全部查詢出List<Car> cars = mapper.selectWithChoose("", null, "");cars.forEach(System.out::println);sqlSession.close();
}

10.6 foreach標簽

批量添加

<insert id="insertBatch">insert into t_car values<foreach collection="cars" item="car" separator=",">(null, #{car.carNum}, #{car.brand}, #{car.guidePrice}, #{car.produceTime}, #{car.carType})</foreach>
</insert>
/*** 批量插入* @param cars* @return*/
int insertBatch(@Param("cars") List<Car> cars);@Test
public void testInsertBatch(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car1 = new Car(null, "1002", "尊界S800", 50.98, "2025-09-01", "智能新能源");Car car2 = new Car(null, "1003", "尊界S801", 50.98, "2025-09-01", "智能新能源");Car car3 = new Car(null, "1004", "尊界S802", 50.98, "2025-09-01", "智能新能源");List<Car> cars = new ArrayList<>();cars.add(car1);cars.add(car2);cars.add(car3);mapper.insertBatch(cars);sqlSession.commit();sqlSession.close();
}

批量刪除

<!--
foreach標簽的屬性:collection:指定數組或者集合item:代表數組或集合中的元素separator:循環之間的分隔符open:foreach循環拼接的所有sql語句的最前面以什么開始。close:foreach循環拼接的所有sql語句的最后面以什么結束。collection="ids”第一次寫這個的時候報錯了,錯誤信息是:[array,arg0]什么意思?map.put("array",數組);map.put("arg”,數組);
-->
<delete id="deleteByIds">delete from t_car where id in(<foreach collection="ids" item="id" separator=",">#{id}</foreach>)/*delete from t_car where id in<!--<foreach collection="ids" item="id" separator="," open="(" close=")">#{id}</foreach>-->*/
</delete><!--根據or進行批量刪除-->
<delete id="deleteByIds2">delete from t_car where<foreach collection="ids" item="id" separator="or">id=#{id}</foreach>
</delete>
/*** 根據in進行批量刪除, foreach標簽* @param ids* @return*/
int deleteByIds(@Param("ids")Long[] ids);@Test
public void testDeleteByIds(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Long[] ids = {5L, 6L, 7L};int count = mapper.deleteByIds(ids);sqlSession.commit();sqlSession.close();
}

10.7 sql標簽與include標簽

sql標簽用來聲明sql片段

include標簽用來將聲明的sql片段包含到某個sql語句當中

<sql id="carCols">id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType</sql><select id="selectAllRetMap" resultType="map">select <include refid="carCols"/> from t_car
</select><select id="selectAllRetListMap" resultType="map">select <include refid="carCols"/> carType from t_car
</select><select id="selectByIdRetMap" resultType="map">select <include refid="carCols"/> from t_car where id = #{id}
</select>

11. MyBatis高級映射及延遲加載

11.1 多對一映射

11.1.1 級聯屬性映射

public class Student {private Integer sid;private String sname;/*多對一,主對象中添加副對象*/private Clazz clazz;
}public class Clazz {private Integer cid;private String cname;
}
<!--多對一映射的第一種方式,級聯屬性映射-->
<!--type="Student"指的是數據庫表與對應的java類的映射-->
<resultMap id="studentResultMap" type="Student"><id property="sid" column="sid"/><result property="sname" column="sname"/><result property="clazz.cid" column="cid"/><result property="clazz.cname" column="cname"/>
</resultMap><select id="selectById" resultMap="studentResultMap">selects.sid, s.sname, c.cid, c.cnamefromt_stu s left join t_clazz c on s.cid = c.cidwheresid = #{sid}
</select>
@Test
public void testSelectById() {SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);Student student = studentMapper.selectById(1);System.out.println(student.getSid());System.out.println(student.getSname());System.out.println(student.getClazz().getCid());System.out.println(student.getClazz().getCname());sqlSession.close();
}

11.1.2 association

其他位置都不需要修改,只需要修改resultMap中的配置:association

<!--多對一映射的第二種方式,association-->
<resultMap id="studentResultMapAssociation" type="Student"><id property="sid" column="sid"/><result property="sname" column="sname"/><!--一個Student對象關聯一個Clazz對象Association:關聯,一個Student對象關聯一個Clazz對象property:指定要映射的POJO類的屬性名javaType:用來指定要映射的java類型--><association property="clazz" javaType="Clazz"><id property="cid" column="cid"/><result property="cname" column="cname"/></association>
</resultMap>

11.1.3 分步查詢(延遲加載)

<!--分步查詢的優點:第一:復用性增強。可以重復利用。(大步拆成N多個小碎步。每一個小碎步更加可以重復利用)第二:采用這種分步查詢,可以充分利用他們的延遲加載/懶加載機制。什么是延遲加載(懶加載),有什么用?延遲加載的核心原理是:用的時候再執行查詢語句。不用的時候不查詢,作用:提高性能。盡可能的不查,或者說盡可能的少查。來提高效率。在mybatis當中怎么開啟延遲加載呢?association標簽中添加fetchType="lazy"注意:默認情況下是沒有開啟延遲加載的。需要設置:fetchType="lazy"這種在association標簽中配置fetchType="lazy",是局部的設置,只對當前的association關聯的sqL語句起作用在實際的開發中,大部分都是需要使用延遲加載的,所以建議開啟全部的延遲加載機制:在mybatis核心配置文件中添加全局配置:lazyLoadingEnabled=true實際開發中的模式:把全局的延遲加載打開。如果某一步不需要使用延遲加載,請設置: fetchType="eager"
-->
<!--1. 完成多對一的分步查詢2. 根據學生的sid查詢學生的所有信息,這些信息當中含有班級id(cid)
-->
<resultMap id="studentResultMapByStep" type="Student"><id property="sid" column="sid"/><result property="sname" column="sname"/><association property="clazz"select="com.hnlg.mapper.ClazzMapper.selectByIdStep2"column="cid"fetchType="eager"/><!--association標簽中的select 指定第二步SQL語句的id-->
</resultMap><select id="selectByIdStep1" resultMap="studentResultMapByStep">select sid, sname, cid from t_stu where sid = #{sid}
</select>
<!--ClazzMapper.xml文件-->
<!--分步查詢第二步:根據cid獲取班級信息-->
<select id="selectByIdStep2" resultType="Clazz">select cid,cname from t_clazz where cid = #{cid}
</select>
@Test
public void testSelectByIdStep1() {SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = mapper.selectByIdStep1(5);
//		System.out.println(student);// 懶加載, 只查看學生的名字System.out.println(student.getSname());// 程序執行到這里,要看班級的名字  訪問才執行這個SQL語句;System.out.println(student.getClazz().getCname());sqlSession.close();
}
全局延遲加載(setting)
<settings><setting name="lazyLoadingEnabled" value="true"/>
</settings>

如果要針對某個特定的sql關閉延遲加載機制,那么就在association標簽中將fetchType改為eager;

11.2 一對多映射

一對多的實現,通常是在一的一方中有List集合屬性。在Clazz類中添加List<Student> stus; 屬性。

public class Student {private Integer sid;private String sname;/*多對一,主對象中添加副對象*/private Clazz clazz;
}public class Clazz {private Integer cid;private String cname;private List<Student> stus;
}

11.2.1 collection

/*** 通過collection進行一對多映射* 根據cid獲取Clazz信息* @param cid* @return*/
Clazz selectByCollection(Integer cid);
<!--一對多映射,通過collection-->
<resultMap id="clazzResultMap" type="Clazz"><id property="cid" column="cid"/><result property="cname" column="cname"/><!--一對多,collection是集合的意思--><!--ofType屬性用來指定集合當中的元素類型--><collection property="stus" ofType="Student"><id property="sid" column="sid"/><result property="sname" column="sname"/></collection>
</resultMap><select id="selectByCollection" resultMap="clazzResultMap">select c.cid, c.cname, s.sid, s.sname from t_clazz c left join t_stu s on c.cid = s.cid where c.cid = #{cid}
</select>

11.2.2 分步查詢

/*** 根據班級編號查詢班級信息。同時班級中所有的學生信息也要查詢。* 分步查詢,第一步,根據班級編號獲取班級信息* @param cid* @return*/
Clazz selectByCidStep1(Integer cid);
<!--一對多查詢-->
<!--分步查詢第一步:根據班級的cid獲取班級信息-->
<resultMap id="clazzResultMapStep" type="Clazz"><id property="cid" column="cid"/><result property="cname" column="cname"/><collection property="stus"select="com.hnlg.mapper.StudentMapper.selectByCidStep2"column="cid"/>
</resultMap><select id="selectByCidStep1" resultMap="clazzResultMapStep">select cid,cname from t_clazz where cid = #{cid}
</select>
<!--StudentMapper.xml文件-->
<!--一對多-->
<select id="selectByCidStep2" resultType="Student">select * from t_stu where cid = #{cid}
</select>
@Test
public void testSelectByCidStep1() {SqlSession sqlSession = SqlSessionUtil.openSession();ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);Clazz clazz = mapper.selectByCidStep1(1000);List<Student> students = clazz.getStus();System.out.println(clazz);System.out.println(students);sqlSession.close();
}

12. MyBatis緩存

緩存的作用:通過減少IO的方式,來提高程序的執行效率。mybatis的緩存:將select語句的查詢結果放到緩存(內存)當中,下一次還是這條select語句的話,直接從緩存中取,不再查數據庫。一方面是減少了IO。另一方面不再執行繁瑣的查找算法。效率大大提升。mybatis緩存包括:

  • 一級緩存:將查詢到的數據存儲到SqlSession中。

  • 二級緩存:將查詢到的數據存儲到SqlSessionFactory中。

  • 或者集成其它第三方的緩存:比如EhCache【Java語言開發的】、Memcache【C語言開發的】等。

緩存只針對于DQL語句,也就是說緩存機制只對應select語句。

12.1 xml文件

<mapper namespace="com.hnlg.mapper.CarMapper"><!--默認情況下,二級緩存機制是開啟的;只需要在對應的xxxMapper.xml文件中添加以下標簽就表示使用二級緩存--><cache/><select id="selectById2" resultType="Car">select * from t_car where id = #{id}</select><select id="selectById" resultType="Car">select * from t_car where id = #{id}</select>
</mapper>

12.2 一級緩存

// 思考:什么時候不走緩存?
// SqlSession對象不是同一個,肯定不走緩存
// 查詢條件不一樣,肯定也不走緩存。// 思考:什么時候一級緩存失效?
// 第一次DQL和第二次DQL之間你做了以下兩件事中的任意一件,都會讓一級緩存清空:
// 1.執行了sqlSession的clearCache()方法,這是手動清空緩存。
// 2.執行了INSERT或DELETE或UPDATE語句。不管你是操作哪張表的,都會清空一級緩存。@Test
public void testSelectById() throws IOException {/*如果要獲取不同的SqlSession對象,不能用這樣的代碼;因為有 private static ThreadLocal<SqlSession> local = new ThreadLocal<>();*//*SqlSession sqlSession = SqlSessionUtil.openSession();SqlSession sqlSession1 = SqlSessionUtil.openSession();*/SqlSessionFactoryBuilder sqlsessionfactorybuilder = new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory = sqlsessionfactorybuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));SqlSession sqlSession = sqlSessionFactory.openSession();SqlSession sqlSession1 = sqlSessionFactory.openSession();/*同一個SqlSession就走一級緩存*/CarMapper mapper1 = sqlSession.getMapper(CarMapper.class);Car car1 = mapper1.selectById(13L);System.out.println(car1);// 手動清空一級緩存// sqlSession.clearCache();CarMapper mapper2 = sqlSession.getMapper(CarMapper.class);Car car2 = mapper2.selectById(13L);System.out.println(car2);
}

12.3 二級緩存

@Test
public void testSelectById2() throws IOException {// 這里只有一個SqlSessionFactory對象,二級緩存對應的就是SqlSessionFactorySqlSessionFactoryBuilder sqlsessionfactorybuilder = new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory = sqlsessionfactorybuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));SqlSession sqlSession1 = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);//這行代碼執行結束之后,實際上數據會緩存到一級緩存當中。(sqlSession1是一級緩存。)Car car1 = mapper1.selectById2(12L);System.out.println(car1);// 如果這里不關閉SqlSession1對象的話,二級緩存中還是沒有數據的。// 如果執行了這行代碼,sqlSession1的一級緩存中的數據會放到二級緩存當中。sqlSession1.close();// 這行代碼執行結束之后,實際上數據會緩存到一級緩存當中。(sqlSession2是一級緩存。)Car car2 = mapper2.selectById2(12L);System.out.println(car2);// 程序執行到這里的時候,會將sqlSession1這個一級緩存中的數據寫入到二級緩存當中// sqlSession1.close();// 程序執行到這里的時候,會將sqlSession2這個一級緩存中的數據寫入到二級緩存當中sqlSession2.close();
}

12.4 MyBatis集成EhCache

集成EhCache是為了代替mybatis自帶的二級緩存。一級緩存是無法替代的。

<!--mybatis集成ehcache的組件-->
<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>1.2.2</version>
</dependency><?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"updateCheck="false"><!--磁盤存儲:將緩存中暫時不使用的對象,轉移到硬盤,類似于Windows系統的虛擬內存--><diskStore path="e:/ehcache"/><!--defaultCache:默認的管理策略--><!--eternal:設定緩存的elements是否永遠不過期。如果為true,則緩存的數據始終有效,如果為false那么還要根據timeToIdleSeconds,timeToLiveSeconds判斷--><!--maxElementsInMemory:在內存中緩存的element的最大數目--><!--overflowToDisk:如果內存中數據超過內存限制,是否要緩存到磁盤上--><!--diskPersistent:是否在磁盤上持久化。指重啟jvm后,數據是否有效。默認為false--><!--timeToIdleSeconds:對象空閑時間(單位:秒),指對象在多長時間沒有被訪問就會失效。只對eternal為false的有效。默認值0,表示一直可以訪問--><!--timeToLiveSeconds:對象存活時間(單位:秒),指對象從創建到失效所需要的時間。只對eternal為false的有效。默認值0,表示一直可以訪問--><!--memoryStoreEvictionPolicy:緩存的3 種清空策略--><!--FIFO:first in first out (先進先出)--><!--LFU:Less Frequently Used (最少使用).意思是一直以來最少被使用的。緩存的元素有一個hit 屬性,hit 值最小的將會被清出緩存--><!--LRU:Least Recently Used(最近最少使用). (ehcache 默認值).緩存的元素有一個時間戳,當緩存容量滿了,而又需要騰出地方來緩存新的元素的時候,那么現有緩存元素中時間戳離當前時間最遠的元素將被清出緩存--><defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/></ehcache>
<!--修改SqlMapper.xml文件中的<cache/>標簽,添加type屬性-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
@Test
public void testSelectById2() throws Exception{SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));SqlSession sqlSession1 = sqlSessionFactory.openSession();CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);Car car1 = mapper1.selectById(83L);System.out.println(car1);sqlSession1.close();SqlSession sqlSession2 = sqlSessionFactory.openSession();CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);Car car2 = mapper2.selectById(83L);System.out.println(car2);
}

13. 逆向工程

<!--定制構建過程-->
<build><!--可配置多個插件--><plugins><!--其中的一個插件:mybatis逆向工程插件--><plugin><!--插件的GAV坐標--><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.4.1</version><!--允許覆蓋--><configuration><overwrite>true</overwrite></configuration><!--插件的依賴--><dependencies><!--mysql驅動依賴--><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.4.0</version></dependency></dependencies></plugin></plugins>
</build>

14.?MyBatis使用PageHelper

14.1 limit分頁

mysql的limit后面兩個數字:

  • 第一個數字:startIndex(起始下標。下標從0開始。)

  • 第二個數字:pageSize(每頁顯示的記錄條數)

假設已知頁碼pageNum,還有每頁顯示的記錄條數pageSize,第一個數字可以動態的獲取嗎?

  • startIndex = (pageNum - 1) * pageSize

標準的mysql分頁SQL

select * 
from tableName ...... 
limit (pageNum - 1) * pageSize, pageSize

<mapper namespace="com.hnlg.mapper.CarMapper"><select id="selectByPage" resultType="Car">select * from t_car limit #{startIndex},#{pageSize}</select>
</mapper>
public interface CarMapper {/*** 通過分頁的方式獲取Car列表* @param startIndex 頁碼* @param pageSize 每頁顯示記錄條數* @return*/List<Car> selectAllByPage(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);
}@Test
public void testSelectByPage() {// 獲取每頁顯示的記錄條數int pageSize = 2;// 顯示第幾頁,頁碼int pageNum = 1;// 計算開始下標int startIndex = (pageNum - 1) * pageSize;SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List<Car> cars = mapper.selectByPage(startIndex, pageSize);cars.forEach(System.out::println);sqlSession.close();
}

14.2 PageHelper插件

1. 引入依賴

<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.3.1</version>
</dependency>

2. mybatis-config.xml中配置插件

<plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

3. 編寫Java代碼

@Test
public void testSelectAll() {SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);/*在執行DQL語句之前,開啟分頁功能*/PageHelper.startPage(1, 5);List<Car> cars = mapper.selectAll();
//		cars.forEach(System.out::println);// 封裝分頁信息對象new PageInfo()// PageInfo對象是PageHelper插件提供的,用來封裝分頁相關的信息對象PageInfo<Car> carPageInfo = new PageInfo<>(cars, 2);System.out.println(carPageInfo);/*PageInfo{pageNum=1, pageSize=5, size=5, startRow=1, endRow=5, total=6, pages=2,list=Page{count=true, pageNum=1, pageSize=5, startRow=0, endRow=5, total=6, pages=2, reasonable=false, pageSizeZero=false}[Car{id=1, carNum='100', brand='寶馬520', guidePrice=41.0, produceTime='2022-09-01', CarType='燃油車'},Car{id=2, carNum='101', brand='奔馳E300L', guidePrice=54.12, produceTime='2022-08-09', CarType='新能源車'},Car{id=3, carNum='102', brand='豐田mirai', guidePrice=40.3, produceTime='2014-10-05', CarType='氫能源'},Car{id=4, carNum='102', brand='豐田mirai', guidePrice=40.3, produceTime='2014-10-05', CarType='氫能源'},Car{id=10, carNum='1001', brand='小米su7', guidePrice=10.0, produceTime='2020-10-10', CarType='新能源'}],prePage=0, nextPage=2, isFirstPage=true, isLastPage=false, hasPreviousPage=false, hasNextPage=true,navigatePages=2, navigateFirstPage=1, navigateLastPage=2, navigatepageNums=[1, 2]}*/sqlSession.close();
}

15. MyBatis注解式開發

public interface CarMapper {@Insert(value="insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")int insert(Car car);
}
public class AnnotationTest {@Testpublic void testInsert() throws Exception{SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));SqlSession sqlSession = sqlSessionFactory.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(null, "1112", "卡羅拉", 30.0, "2000-10-10", "燃油車");int count = mapper.insert(car);System.out.println("插入了幾條記錄:" + count);sqlSession.commit();sqlSession.close();}
}

15.2 不開啟駝峰命名自動映射

@Delete("delete from t_car where id = #{id}")
int deleteById(Long id);@Update("update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType} where id=#{id}")
int update(Car car);@Select("select * from t_car where id = #{id}")
/*解決駝峰命名自動映射的情況, id為主鍵*/
@Results({@Result(column = "id", property = "id", id = true),@Result(column = "car_num", property = "carNum"),@Result(column = "brand", property = "brand"),@Result(column = "guide_price", property = "guidePrice"),@Result(column = "produce_time", property = "produceTime"),@Result(column = "car_type", property = "carType")
})
Car selectById(Long id);

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/921576.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/921576.shtml
英文地址,請注明出處:http://en.pswp.cn/news/921576.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

WebRTC開啟實時通信新時代

摘要&#xff1a;WebRTC&#xff08;Web實時通信&#xff09;是一項開源技術&#xff0c;支持瀏覽器直接進行低延遲音視頻通信和數據傳輸&#xff0c;無需安裝插件。其核心技術包括RTCPeerConnection&#xff08;建立點對點連接&#xff09;、MediaStream&#xff08;媒體流處理…

【51單片機8*8點陣顯示箭頭動畫詳細注釋】2022-12-1

緣由51單片機實現8*8滾動箭頭的程序,運行時什么圖案都沒有,甚至根本不亮 - 24小時必答區 #include<reg52.h> unsigned char code M[]{0xff,0xff,0xfe,0xfd,0xf8,0xfd,0xfe,0xff,0xff,0xff,0xfd,0xfb,0xf0,0xfb,0xfd,0xff,0xff,0xff,0xfb,0xf7,0xe0,0xf7,0xfb,0xff,0xff,0…

手撕Redis底層3-持久化機制與集群化方案

1.Redis持久化機制Redis設計了兩種持久化落盤機制&#xff1a;RDB和AOF1.1 RDB持久化RDB持久化是Redis的數據快照&#xff0c;簡單來說就是把內存中的所有數據都記錄到磁盤中&#xff0c;當Redis實例故障重啟后&#xff0c;從磁盤中讀取快照文件來恢復數據。快照文件稱為RDB文件…

mysql中null值對in子查詢的影響

1、場景 有這樣一個查詢&#xff0c;有些時候是正確的&#xff0c;有些時候沒報錯但是又查詢不到數據&#xff0c;分析數據排查后發現當user_id字段存在null值的時候查詢不到數據。select * from table1 where id in (select user_id from talbe2 where status1);2、問題 為什么…

如何在 tortoise-orm 內使用 JSON_EXTRACT

先說結論&#xff1a; # 假設 JsonField 名稱為 data&#xff0c;內容為 {"info": {"path": "我的資源創建"}} qs qs.filter(data__filter{"info.path": "我的資源創建"})我查看了 tortoise-orm 官方文檔&#xff0c;沒有這…

西門子S7-200 SMART PLC:編寫最基礎的“起保停”程序

一、什么是“起保停”電路&#xff1f;“起保停”是“啟動-保持-停止”的簡稱&#xff0c;也稱為“自鎖電路”。它是繼電器控制系統和PLC程序中最基本、最核心的控制邏輯。啟動 (Start): 由一個點動按鈕&#xff08;常開觸點&#xff09;觸發&#xff0c;使設備運行。保持 (H…

漏洞修復 Nginx SSL/TLS 弱密碼套件

掃描結果 [rootlocalhost nmap]# docker run --rm -v $(pwd)/results:/results securecodebox/nmap nmap --script ssl-enum-ciphers -p 443 xxx.cn -oX /results/output_0904.xml Starting Nmap 7.80 ( https://nmap.org ) at 2025-09-04 05:02 UTC Nmap scan report for xxx.…

ChartGPT深度體驗:AI圖表生成工具如何高效實現數據可視化與圖表美化?

最近幫運營同事做季度數據報告時&#xff0c;我差點在圖表樣式上栽跟頭 —— 明明數據都算好了&#xff0c;用 Excel 調柱狀圖的顏色、字體、坐標軸標簽&#xff0c;來回改了快半小時&#xff0c;要么字體太大擠在一起&#xff0c;要么顏色搭配顯臟&#xff0c;運營催得急&…

深入理解 JVM 字節碼文件:從組成結構到 Arthas 工具實踐

在 Java 技術體系中&#xff0c;JVM&#xff08;Java 虛擬機&#xff09;是實現 “一次編寫&#xff0c;到處運行” 的核心。而字節碼文件作為 Java 代碼編譯后的產物&#xff0c;是 JVM 執行的 “原材料”。今天&#xff0c;我們就從字節碼文件的組成結構講起&#xff0c;再結…

SoundSource for Mac 音頻控制工具

SoundSource for Mac 是一款音頻控制工具&#xff0c;中文常被稱為 音頻源管理器。它能夠精確控制系統與應用程序的音量、輸出設備和音效處理&#xff0c;讓用戶獲得比 macOS 原生更靈活的音頻管理體驗。SoundSource 既適合音樂發燒友&#xff0c;也適合日常辦公和影音娛樂用戶…

云平臺面試內容(二)

5. VPC、子網、路由、NAT網關、安全組、網絡ACL 區別與網絡隔離設計 概念區別: VPC(虛擬私有云): VPC是在公有云上劃分出的一個用戶專屬的虛擬網絡環境,相當于用戶在云上的私有數據中心。用戶可以自定義VPC的IP地址段、路由策略等。不同VPC網絡隔離,默認互不相通,確保資…

2023 arXiv MapperGPT: Large Language Models for Linking and Mapping Entities

論文基本信息 題目&#xff1a;MapperGPT: Large Language Models for Linking and Mapping Entities作者&#xff1a;Nicolas Matentzoglu, J. Harry Caufield, Harshad B. Hegde, Justin T. Reese, Sierra Moxon, Hyeongsik Kim, Nomi L. Harris, Melissa A Haendel, Christo…

Docker入門到精通:從零基礎到生產部署

前言&#xff1a;為什么你需要學習Docker&#xff1f; 想象一下&#xff0c;你開發了一個應用程序&#xff0c;在你的電腦上運行完美&#xff0c;但當你把它交給同事或部署到服務器時&#xff0c;卻出現了各種奇怪的問題。這就是著名的"在我機器上能運行"問題。 Do…

HOT100--Day15--98. 驗證二叉搜索樹,230. 二叉搜索樹中第 K 小的元素,199. 二叉樹的右視圖

HOT100–Day15–98. 驗證二叉搜索樹&#xff0c;230. 二叉搜索樹中第 K 小的元素&#xff0c;199. 二叉樹的右視圖 每日刷題系列。今天的題目是《力扣HOT100》題單。 題目類型&#xff1a;二叉樹。 關鍵&#xff1a;要深刻理解《遞歸》 98. 驗證二叉搜索樹 思路&#xff1a; …

獨角數卡對接藍鯨支付平臺實現個人

目錄 什么是獨角數卡&#xff1f;安裝部署教程一、獨角數卡安裝二、獨角數卡支付配置三、獨角數卡BUG修復 什么是獨角數卡&#xff1f; ? ? ? ? ? ? ? 獨角數卡(Dujiaoka)?是一款基于Laravel框架開發的開源式站長自動化售貨解決方案&#xff0c;主要用于虛擬商品和數字…

人工智能常見分類

人工智能的分類方式多樣&#xff0c;以下是一些常見的分類方法及具體類型&#xff1a; 一、按功能目標分類 弱人工智能&#xff08;ANI&#xff0c;Narrow AI&#xff09;&#xff1a;專注于單一任務&#xff0c;無自主意識&#xff0c;如圖像識別&#xff08;人臉解鎖&#xf…

PO BAPI bapi_po_create1

當執行BAPI時,需要導入增強字段,其中增強字段包含數值型號字段時,需要增強BADI::ME_BAPI_PO_CUST 代碼如下: 記錄一下,下次自己繼續用 bapi處: ls_te_item-po_item = lv_item.ls_te_item-zz001 = 11.ls_te_item-zz005 = 22.ls_te_item-zz008 = 33.ls_te_item-zz009 = 44…

棧欺騙技術的作用是什么?

好的&#xff0c;我們來詳細解釋一下“棧欺騙技術”&#xff08;Stack Spoofing&#xff09;的作用。簡單來說&#xff0c;棧欺騙技術的核心作用是隱藏程序&#xff08;尤其是惡意軟件或安全工具&#xff09;的真實調用鏈&#xff0c;使其逃避基于棧回溯&#xff08;Stack Walk…

Nano-banana 模型對接教程:最懂創作者的 AI 模型,比GPT-4o還強!

Nano-banana 模型對接教程&#xff08;含 BaseURL&#xff09; Nano Banana 是谷歌推出的革命性 AI 圖像編輯模型&#xff0c;代表了從"AI繪畫工具"到"AI創意伙伴"的范式轉移。它不再是被動執行指令&#xff0c;而是能深刻理解已有圖像的上下文、光影、物…

CEEMDAN-PSO-CNN-GRU 鋰電池健康狀態預測matlab

代碼說明 這個實現包含以下主要組成部分: 數據準備:加載并預處理鋰電池容量數據,劃分訓練集和測試集 CEEMDAN分解:將原始信號分解為多個本征模態函數(IMF)和一個殘差項 PSO優化:使用粒子群算法優化CNN-GRU網絡的超參數 CNN-GRU模型:構建并訓練卷積神經網絡與門控循環…