基礎MyBatis問題以去看MyBatis基礎。
使用log4j設置日志在控制臺打印SQL語句及其執行信息
也可以使用MyBatis基礎中用的slf4j。
在pom.xml文件中引入log4j坐標依賴
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version>
</dependency>
在resourecs目錄下設置log4j.properties文件配置信息用于在控制臺查看SQL語句的執行和拼接情況
log4j.rootLogger = INFO,CONSOLE,FILElog4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayoutlog4j.appender.CONSOLE.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%nlog4j.appender.FILE=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.FILE.DatePattern='.'yyyy-MM-dd
log4j.appender.FILE.File=D:/logs/mybatis.log
log4j.appender.FILE.layout = org.apache.log4j.PatternLayoutlog4j.appender.FILE.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%c]-[%p]- %m%n
然后在MyBatis核心配置文件配置,如下:
MyBatis核心配置文件配置半自動映射和全自動映射
<!--mybatis全局配置-->
<settings><!-- 如果使用log4j打印SQL語句,則需要配置 value 的值 --><!-- value="STDOUT_LOGGING" 使用日志打印SQL語句(會在控制臺自動打印執行的SQL語句)--><setting name="logImpl" value="STDOUT_LOGGING"/><!-- value="LOG4J" 不打印SQL語句,只打印日志信息-->
<!-- <setting name="logImpl" value="LOG4J"/>--><!--默認情況下,MyBatis 會自動進行結果映射,即把數據庫查詢結果映射為 Java Bean。但是,如果數據庫字段與 Java Bean 的屬性名不一致,則需要設置自動映射為NONE,即不進行自動映射,此時需要手動進行映射。默認:半自動映射(封裝),即自動把sql配置xml文件中select查詢出來的數據封裝成resultType或resultMap指定的JavaBean對象,如果封裝了多個對象,則自動變成List集合返回。NONE:關閉自動映射(封裝),關閉后,需要使用resultMap進行手動映射。FULL:完全自動映射(嵌套封裝,一對一、一對多,對象套對象)-->
<!-- <setting name="autoMappingBehavior" value="NONE"/>--><setting name="autoMappingBehavior" value="FULL"/>
</settings>
一、查
resultType:返回值類型,會自動把查出來的數據自動封裝成該類型對象并返回,如果有多個,則會把封裝的對象放到一個list集合中并返回。
自動封裝時會按查詢的數據庫表的字段先后順序讓該數據庫表的字段名與要封裝成的對象的屬性名進行匹配,若匹配上則會把該數據封裝到對象中。(尤其是兩張有相同字段名表聯合查詢,并且MyBatis進行自動封裝時,會出現該字段的數據封裝錯誤的問題,可見 1.6一對一 )。
1.1 #{} 和 ${}
dao層接口方法
public Provider findProviderById(Integer id);
SQL映射
#{}
是將參數數據以占位符的形式與SQL語句進行拼接,防止SQL注入攻擊。${}
是將參數數據以原樣輸出的形式與SQL語句進行拼接,但是不建議使用,容易出現SQL注入問題。- {}中的參數名要與接口方法中的參數名一致
- sql語句最后的分號可不寫。
(可以看日志在控制臺輸出執行的所拼接的sql語句,見Mybatis基礎)
<!--id要與接口中方法名一致-->
<!--parameterType:參數類型 可不寫-->
<!--resultType:返回值類型,會把查出來的數據封裝成該類型對象并返回,如果有多個,則會把封裝的對象放到一個list集合中并返回-->
<!--sql語句最后的分號可不寫-->
<select id="findProviderById" resultMap="ProviderMap"><!--#{}是將參數數據以占位符的形式與SQL語句進行拼接,防止SQL注入攻擊${}是將參數數據以原樣輸出的形式與SQL語句進行拼接,但是不建議使用,容易出現SQL注入問題-->select id,proCode,proName,proDesc from smbms_provider a where a.id=#{id}
</select>
1.2 特殊字符處理
特殊字符(比如>、<等特殊符號)處理,這里以查詢id小于指定id的SQL語句為例:
- 轉義字符:如
<
會被認為是一個標簽的開始,需要轉義為<
,一般用于少量使用特殊字符的SQL語句(>轉義對應>
,>能正常使用,可以不用轉義)。 - CDATA區(當作純文本處理):寫CD會有提示,如在里面寫<號 <![CDATA[ < ]]>,一般用于大量使用特殊字符的SQL語句。
<select id="selectById" resultMap="brandResultMap"><!--使用轉義-->select * from tb_brand where id <= #{id};<!--使用CDATA區-->select * from tb_brand where id <![CDATA[ < ]]> #{id};
</select>
1.3 模糊查詢不能用單引號
注意:這里模糊查詢不能寫單引號,如:‘%${proName}%’,這樣寫會報錯。要用concat拼接,concat(‘%’,#{pname},‘%’)。
<select id="findList" resultType="Provider">select id,proCode,proName,proDescfrom smbms_provider awhere proName like concat('%',#{pname},'%')and proDesc like concat('%',#{pdesc},'%')
</select>
1.4 自動封裝數據問題
如果數據庫表的字段名稱(列名)和resultType指定的實體類的屬性名稱不一樣,則不能自動封裝數據(即若對應不上則封裝后數據為null)。
有以下三個方案可以解決:
1.4.1 起別名
給與實體類中屬性名不一樣的列名起別名,這個別名要與對應的實體類中的屬性名一致。
缺點:每次查詢都要定義一次別名。
<select id="selectAll" resultType="brand"><!--brand_name是列名,要起別名與Brand類中屬性brandName的屬性名一致,否則無法封裝-->select id, brand_name as brandName, company_name as companyName, ordered, description, status from tb_brand;
</select>
1.4.2 resultMap(最常用)
數據庫字段與要封裝成的實體類屬性不一致才需要字段映射。
如果關閉了自動映射,則只能通過resultMap封裝(映射)指定的數據
- 定義
<resultMap>
標簽:- type:要封裝成的數據類型
<id property="id" column="id"/>
用來完成主鍵字段的映射。<result property="brandName" column="brand_name"/>
用來完成一般字段的映射,也能用于完成主鍵字段的映射(它倆只是為了區分字段,實際上沒區別)。- property:實體類的屬性名。
- column:數據庫表的字段名(列名)。
- 若property和column的值一樣,則可以不寫這個字段映射。
- jdbcType:數據庫字段類型。
- javaType:java字段類型。
- jdbcType與javaType(可不寫):告訴mybatis如何將數據庫字段映射到java字段中,只是為了比較數據類型是否一致,一般都是寫jdbcType=“VARCHAR” javaType=“String”。
- 在
<select>
標簽中,使用resultMap屬性替換resultType屬性,并設置為對應resultMap的id。
<resultMap id="brandResultMap" type="brand"><!--數據庫字段與實體類屬性不一致才需要字段映射--><!--id用來完成主鍵字段的映射,如下。若數據庫字段名與實體類屬性名一致,可以不用寫--><!--<id property="id" column="id"/>--><!--result用來完成一般字段的映射--><result property="brandName" column="brand_name" jdbcType="VARCHAR" javaType="String"/><result property="companyName" column="company_name"/>
</resultMap>
<select id="selectById" resultMap="brandResultMap">select * from tb_brand where id=#{id};
</select>
1.4 動態SQL
1.4.1 where、if
動態SQL條件查詢,如下案例
if:條件判斷,滿足條件才會拼接其中寫的SQL。
test:判斷條件(多個判斷條件間要用英文 and 或 or 拼接,不能用&&和||)
問題:若第一個條件判斷不成立,則該條件下的SQL不會拼接,導致后面拼接的SQL語句錯誤(原本的第二個條件變成了第一個條件,若該條件滿足,則會以and開頭,SQL語句錯誤)
解決:
- 恒等式(方法較笨):所有條件開頭都加and,并且讓 1=1 這個恒等式作為第一個條件。
<where>
標簽(常用):- 替換where關鍵字,會自動把第一個條件中SQL開頭的and去掉(如果and存在)。
- 第一個條件中的SQL開頭可不加and,后面的條件中的SQL必須以and開頭。
- 如果
<where>
標簽中沒有要拼接的條件內容,則執行SQL時不會添加where關鍵字。
<select id="selectByCondition" resultMap="brandResultMap">select *from tb_brand
<!-- where--><!-- 若參數status為null,則該條件不會拼接,導致后面拼接的SQL語句錯誤(原本的第二個條件變成了第一個條件,且以and開頭,SQL語句錯誤)-->
<!-- <if test="status!=null">-->
<!-- status = #{status}-->
<!-- </if>-->
<!-- --><!--用恒等式-->
<!-- 1 = 1-->
<!-- <if test="status!=null">-->
<!-- and status = #{status}-->
<!-- </if>-->
<!-- <if test="companyName!=null and companyName!=''">-->
<!-- and company_name like #{companyName}-->
<!-- </if>-->
<!-- <if test="brandName!=null and brandName!=''">-->
<!-- and brand_name like #{brandName}-->
<!-- </if>--><where><if test="status!=null">status = #{status}</if><if test="companyName!=null and companyName!=''">and company_name like #{companyName}</if><if test="brandName!=null and brandName!=''">and brand_name like #{brandName}</if></where></select>
1.4.2 choose、when、otherwise
相當于Java中的if、elseif。
<select id="selectByConditionSingle" resultMap="brandResultMap">select id, brand_name as brandName, company_name as companyName, ordered, description, statusfrom tb_brand
<!-- where-->
<!-- <choose><!–最多只會拼接其中一個條件,誰先滿足就拼接哪個條件–>-->
<!-- <when test="status!=null">-->
<!-- status = #{status}-->
<!-- </when>-->
<!-- <when test="companyName!=null and companyName!=''">-->
<!-- company_name like #{companyName}-->
<!-- </when>-->
<!-- <when test="brandName!=null and brandName!=''">-->
<!-- brand_name like #{brandName}-->
<!-- </when>-->
<!-- <otherwise><!–前面都不滿足時執行,避免沒有傳值時報錯–>-->
<!-- 1 = 1-->
<!-- </otherwise>-->
<!-- </choose>--><!--如果不想用<otherwise>,則可以用<where>把<choose>包裹住--><where><choose><!--最多只會拼接其中一個條件,誰先滿足就拼接哪個條件--><when test="status!=null">status = #{status}</when><when test="companyName!=null and companyName!=''">company_name like #{companyName}</when><when test="brandName!=null and brandName!=''">brand_name like #{brandName}</when></choose></where>
</select>
1.5 SQL片段
用于把一些重復使用的SQL語句的片段抽取出來,需要用時就調用。
使用<sql>
標簽定義SQL片段,id:唯一標識,調用時需要。
在SQL語句中使用<include>標簽
,通過refid指定要調用的SQL片段。
<!--sql片段-->
<sql id="brand_column">id, brand_name as brandName, company_name as companyName, ordered, description, status
</sql>
<select id="selectAll" resultType="brand"><!--沒使用sql片段--><!--select id, brand_name as brandName, company_name as companyName, ordered, description, status from tb_brand;--><!--使用sql片段--><!--使用include標簽的refid屬性調用sql片段-->select <include refid="brand_column"/> from tb_brand;
</select>
1.6 一對一(association、數據封裝錯誤)
一個收獲地址對應一個用戶。
需要在Address類中添加一個User類的對象屬性,用于存儲用戶信息,如:
private User u;//存儲用戶對象信息
dao層接口方法
public List<Address> findAddressAll();
SQL映射
<select id="findAddressAll" resultMap="AddressMap">selecta.id,a.contact,a.addressDesc,a.tel,a.userId,b.id,b.userNamefrom smbms_address aleft join smbms_user bon a.userId=b.id
</select>
<resultMap id="AddressMap" type="Address">
<!-- jdbcType:數據庫字段類型-->
<!-- javaType:java字段類型-->
<!-- jdbcType與javaType的作用:告訴mybatis如何將數據庫字段映射到java字段中,只是為了比較數據類型是否一致,可不寫,一般都是寫jdbcType="VARCHAR" javaType="String"--><id property="id" column="id"/><result property="contact" column="contact" jdbcType="VARCHAR" javaType="String"/><result property="addressDesc" column="addressDesc"/><result property="tel" column="tel"/><!-- 一對一 property:指定封裝實體類對象的屬性名 javaType:指定封裝的java對象類型--><!--如果想使用全自動映射,則必須有一個空的association,但與其寫一個空的association不如用半自動映射,另寫一個resultMap并引進來,進行手動封裝--><association property="u" javaType="User" resultMap="UserMap"><!--如果是半自動映射,若association里面不設置映射,則不會把這里面的數據封裝--><!--<id property="userName" column="userName"/>--><!--自動封裝時會按查詢的數據庫表的字段先后順序讓該數據庫表的字段名與要封裝成的對象的屬性名進行匹配,因為先查的是smbms_address的表的id,且User類的id屬性名與smbms_address表的id字段名一致,因此會先把address的id封裝進user對象的id值,而不是封裝后面查詢的address_user的id,從而導致數據封裝錯誤除非手動封裝,如下,或者把user表的id字段改為user_id-->
<!-- <id property="id" column="userId"/>--></association>
</resultMap>
<resultMap id="UserMap" type="User"><id property="id" column="userId"/><id property="userName" column="userName"/>
</resultMap>
1.7 一對多(collection)
一個用戶對應多個收獲地址。
需要在User類中添加一個list集合屬性,用于存儲收獲地址信息。如:
private List<Address> addressList;//存儲收獲地址信息
dao層接口方法
public User findUserById(Integer id);
SQL映射
<select id="findUserById" resultMap="UserMap">select a.id,a.userName,b.id as addrId,b.contact,b.addressDescfrom smbms_user aleft join smbms_address bon a.id = b.userIdwhere a.id = #{id};
</select>
<!--id:唯一標識 type:映射的類型,支持別名-->
<resultMap id="UserMap" type="User"><!--若property和column的值一樣,則可以不寫這個字段映射。--><!--<id property="id" column="id"/>--><!--<result property="userName" column="userName"/>--><!--一對多 addressList:(User類)屬性集合的名稱 ofType:要封裝成的對象(Address類)--><!--理解:就是把查詢出來的數據中列名(或別名)與Address類的屬性名對應的每一行數據封裝成一個Address對象放到User類的addressList集合屬性中--><collection property="addressList" ofType="Address"><id property="id" column="addrId"/><result property="contact" column="contact"/><result property="addressDesc" column="addressDesc"/></collection>
</resultMap>
1.8 foreach
自動遍歷傳過來的集合、數組、Map的key所對應的集合或數組
<foreach>
標簽:
item:獲取每次循環的對象值
collection:指定循環集合list、array、map的key
index作用:獲取集合元素索引,可充當參數使用,如:#{index}
- 迭代List集合或數組時:index表示當前元素在List集合或數組中的下標位置(從 0 開始)
- 迭代Map集合時:index表示當前元素的鍵(key)
open:前綴
close:后綴
separator:指定集合元素之間的分隔符
準備的sql片段
<sql id="userCols">id,userCode,userName,birthday
</sql>
1.8.1 傳入集合
dao層接口方法
public List<User> findUserList1(List<Integer> ids);
SQL映射
<select id="findUserList1" resultType="User">select<include refid="userCols"/>from smbms_userwhere id in<!--(1,2,3,4,5)--><foreach item="id" collection="list" index="index" open="(" close=")" separator=",">#{id}</foreach>
</select>
1.8.2 傳入數組
dao層接口方法
public List<User> findUserList2(Integer[] ids);
SQL映射
<select id="findUserList2" resultType="User">select<include refid="userCols"/>from smbms_userwhere id in<foreach item="id" collection="array" index="index" open="(" close=")" separator=",">#{id}</foreach>
</select>
1.8.3 傳入 map
dao層接口方法
public List<User> findUserList3(Map<String,Object> map);
SQL映射
<select id="findUserList3" resultType="User">select<include refid="userCols"/>from smbms_userwhere id in<!--這里ids是map的一個鍵,其值是一個數組--><foreach item="id" collection="ids" index="index" open="(" close=")" separator=",">#{id}</foreach>and userName = #{name}
</select>
二、增、刪、改
增、刪、改默認返回int,無法修改,不用寫resultType,寫了會報錯。
2.1 增(使用trim實現動態SQL)
dao層接口方法
public int saveUser(User user);
SQL映射
<trim>
標簽:
- prefix:給sql語句拼接的前綴。
- suffix:給sql語句拼接的后綴。
- prefixOverrides:自動去除sql語句前面的關鍵字或者字符(如果存在),該關鍵字或者字符由prefixOverrides屬性指定,假設該屬性指定為"AND",當sql語句的開頭為"AND",trim標簽將會去除該"AND"。
- suffixOverrides:自動去除sql語句前面的關鍵字或者字符(如果存在),該關鍵字或者字符由prefixOverrides屬性指定。
<insert id="saveUser"><!--為了實現動態SQL,可以使用trim標簽-->insert into smbms_user<trim prefix="(" suffixOverrides="," suffix=")"><if test="userCode!=null">userCode,</if><if test="userName!=null">userName,</if><if test="userPassword!=null">userPassword,</if><if test="gender!=null">gender,</if><if test="birthday!=null">birthday,</if></trim>values<trim prefix="(" suffixOverrides="," suffix=")"><if test="userCode!=null">#{userCode},</if><if test="userName!=null">#{userName},</if><if test="userPassword!=null">#{userPassword},</if><if test="gender!=null">#{gender},</if><if test="birthday!=null">#{birthday},</if></trim>
</insert>
2.2 改(set標簽)
dao層接口方法
public int updateUser(User user);
SQL映射
直接使用set關鍵字會導致最后拼接SQL時要修改的內容最后多一個逗號,這就需要用到
<set>
標簽,會自動去除拼接的SQL最后多余的逗號。
<set>
標簽只在修改里面用。
<update id="updateUser">update smbms_user<!--直接使用set會導致最后拼接SQL時要修改的內容最后多一個逗號,這就需要用到<set>標簽,會自動去除拼接的SQL最后多余的逗號<set>標簽只在修改里面用--><set><if test="userCode!=null">userCode=#{userCode},</if><if test="userName!=null">userName=#{userName},</if><if test="userPassword!=null">userPassword=#{userPassword},</if><if test="gender!=null">userPassword= #{gender},</if><if test="birthday!=null">birthday= #{birthday},</if></set><where>id=#{id}</where>
</update>
2.3 刪
執行刪除SQL的映射沒什么要注意的,這里就省略了。
三、接口方法傳遞參數
3.1 單個參數
一個參數,一般是不會用@Param注解的。
dao層接口方法
public Provider findProviderById(Integer id);
SQL映射
<select id="findProviderById" resultMap="ProviderMap">select id,proCode,proName,proDesc from smbms_provider a where a.id=#{id}
</select>
3.2 多個參數
多個參數(兩個及以上):必須使用注解,相當于給參數起別名,否則SQL映射文件不認。
注解:@Param(value=“proName”),value=可省略,給參數指定別名,并在SQL配置xml文件中使用別名#{proName},#{proDesc}
dao層接口方法
public List<Provider> findList(@Param("pname") String proName,@Param(value="pdesc") String proDesc);
SQL映射
<select id="findList" resultType="Provider">select id,proCode,proName,proDescfrom smbms_provider awhere proName like concat('%',#{pname},'%')and proDesc like concat('%',#{pdesc},'%')
</select>
3.3 對象參數
- 如果傳的是一個實體類對象,則sql中可直接使用這個實體類對象的屬性名。
- 如果傳的實體類對象使用@Param注解起別名,則sql中必須使用#{別名.參數名}。
dao層接口方法
//這里一個參數,正常是不會用@Param注解的,只是為了演示在使用注解的前提下,在sql配置xml文件中必須使用#{別名.屬性名}進行接收傳遞的參數
public int addProvider(@Param(value="pro") Provider provider);
SQL映射
<insert id="addProvider">insert into smbms_provider(proCode,proName,proDesc) values(#{pro.proCode},#{pro.proName},#{pro.proDesc})
</insert>
3.4 使用Map集合能存所有參數
dao層接口方法
public List<Provider> findListByMap(Map<String,Object> maps);
SQL映射
使用map的key作為參數進行傳遞
<select id="findListByMap" resultType="Provider"><!--使用map的key作為參數進行傳遞-->select id,proCode,proName,proDescfrom smbms_provider awhere proName like concat('%',#{name},'%')and proDesc like concat('%',#{desc},'%')
</select>