MyBatis中#{}占位符與${}拼接符的用法說明
1、關於#{}占位符
先來看以下的示例,該示例是MyBatis中的SQL映射配置文件(Mapper配置文件),在該配置中使用瞭#{}占位符。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="test"> <!-- 根據用戶編號,查詢單個用戶實體 --> <select id="findUserById" parameterType="int" resultType="com.pjb.mybatis.po.User"> SELECT * FROM tb_user WHERE id = #{id} </select> <!-- 新增用戶 --> <insert id="insertUser" parameterType="com.pjb.mybatis.po.User"> INSERT INTO tb_user(user_name,blog_url,remark) VALUES(#{userName},#{blogUrl},#{remark}); </insert> </mapper>
在SQL映射配置文章中,輸入參數需要用占位符來標識對應的位置。
在傳統的JDBC編程中,占位符用“?”來標識,然後在加載SQL之前按照“?”的位置設置參數。
而“#{}”在MyBatis中也代表一種占位符,該符號接受輸入參數,在大括號中編寫參數名稱來接受對應參數。
“#{}”接受的輸入參數的類型可以是簡單類型、普通JavaBean或者HashMap。
當接受簡單類型時,“#{}”中可以寫“value”或者其他任意名稱。
如果接受的是JavaBean,它會通過OGNL讀取對象中的屬性值。
2、關於${}拼接符
再來看以下的示例,該示例是MyBatis中的SQL映射配置文件(Mapper配置文件),在該配置中使用瞭${}拼接符。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="test"> <!-- 根據用戶名稱,模糊查詢用戶列表 --> <select id="findUserByUserName" parameterType="java.lang.String" resultType="com.pjb.mybatis.po.User"> SELECT * FROM tb_user WHERE user_name LIKE '%${value}%' </select> </mapper>
在SQL映射配置文件中,有時候需要拼接SQL語句。例如在模糊查詢的時候,就需要在查詢條件的兩側拼接兩個“%”字符串,這個時候如果使用“#{}”占位符是不行的。在MyBatis中,“${}”在SQL配置文件中代表一個“拼接符號”,可以在原有SQL語句上拼接新的符合SQL語法的語句。
但是要註意的是,使用“${}”拼接的SQL語句,會引起SQL註入,所以一般不建議使用“${}”。
“${}”接受輸入參數的類型可以是簡單類型、普通JavaBean或者HashMap。
當接受簡單類型時,“${}”中隻能寫“value”,而不能寫其他任意名稱。
如果接受的是JavaBean,它會通過OGNL讀取對象中的屬性值。
另外,在MyBatis 3.4.2之後,還可以在“${}”拼接符中設置一個默認值,格式如下:
${屬性:默認值}
即在所需引入的屬性名的後面添加“:”引號,然後緊跟著填寫屬性不存在或為空時的默認值。
【示例】設置“${}”拼接符的默認值。
(1)創建數據庫連接的屬性文件(db.properties)
在src目錄下創建db.propertie屬性文件,並使用“#”符號將屬性項jdbc.username和jdbc.password註釋掉,表示這兩個屬性項未進行配置。
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/db_admin?useSSL=false& #jdbc.username=root #jdbc.password=123456
(2)啟用拼接符默認值的配置
要使用“${}”拼接符中設置一個默認值,需要在properties標簽中設置啟用拼接符默認值的配置項。
在MyBatis配置文件(mybatis-config.xml)中,使用<properties>標簽加載數據庫連接屬性文件(db.properties),並在該標簽中設置啟用拼接符默認值的配置項,該配置如下:
<!-- 加載用於數據庫配置的屬性文件 --> <properties resource="db.properties"> <!-- 啟用默認值特性,這樣${}拼接符才可以設置默認值 --> <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> </properties>
註意:在MyBatis配置文件(mybatis-config.xml)中,<properties>標簽的配置必須寫在<settings>標簽的上面,因為MyBatis中的配置,不但有類型限制,還有順序限制。
必須按照:<properties>、<settings>、<typeAliases>、<typeHandlers>、…順序排放。
(3)編寫數據庫配置信息,並使用“${}”拼接符的默認值。
說明:
由於在db.propertie屬性文件中,已經將jdbc.username和jdbc.password屬性項註釋掉瞭,然後在上面的配置信息中,給username和password配置項中的“${}”拼接符中設置默認值,這樣程序在啟動時,就會讀取默認值。
補充:MyBatis映射——SQL占位符及傳參
簡介
本篇主要講述Mybatis映射SQL通過#{}獲取引入類型參數的屬性值及通過@Param註解指定名稱傳參。
關於占位符與字符拼接:
占位符:占位符就是在某個地方占領一個位置,把它單獨作為某個東西,比如這裡就是把它作為 值。
#{}表示一個占位符號,通過#{}可以實現 preparedStatement 向占 位符中設置值, 自動進行 java
類型和 jdbc 類型轉換。#{}可以有效防止 sql 註入。 #{}可以接 收簡單類型值或 pojo 屬性值。 如果 parameterType 傳輸單個簡單類型值,#{} 括號中可以是 value 或其它名稱。
字符拼接:字符拼接就是簡單的對字符串拼接。沒有特殊的其它含義。
表 示 拼 接 s q l 串 , 通 過 可 以 將 p a r a m e t e r T y p e 傳 入 的 內 容 拼 接 在 s q l 中 且 不 進 行 j d b c 類 型 轉 換 , 可 以 將 p a r a m e t e r T y p e 傳 入 的 內 容 拼 接 在 s q l 中 且 不 進 行 j d b c 類 型 轉 換 , 可 以 接 收 簡 單 類 型 值 或 p o j o 屬 性 值 , 如 果 p a r a m e t e r T y p e 傳 輸 單 個 簡 單 類 型 值 , {}表示拼接 sql 串,通過可以將parameterType傳入的內容拼接在sql中且不進行jdbc類型轉換,可以將parameterType傳入的內容拼接在sql中且不進行jdbc類型轉換,{}可以接收簡單類型值或 pojo 屬性值,如果 parameterType 傳輸單個簡單類型值,表示拼接sql串,通過可以將parameterType傳入的內容拼接在sql中且不進行jdbc類型轉換,可以將parameterType傳入的內容拼接在sql中且不進行jdbc類型轉換,可以接收簡單類型值或pojo屬性值,如果parameterType傳輸單個簡單類型值,{}括號中隻能是 value。
關於@Param:
在用註解來簡化xml配置的時候(比如Mybatis的Mapper.xml映射文件中的sql參數引入);
@Param註解的作用是給參數命名,參數命名後就能根據名字得到參數值,正確的將參數傳入sql語句中(一般通過#{}的方式,${}會有sql註入的問題)。
#{}: 解析為一個 JDBC 預編譯語句(prepared statement)的參數標記符,一個 #{ } 被解析為一個參數占位符 。 ${}: 僅僅為一個純碎的 string 替換,在動態 SQL 解析階段將會進行變量替換。
MyBatis 的真正強大在於它的映射語句
Mapper.xml映射文件中定義瞭操作數據庫的sql,每個sql是一個statement,映射文件是mybatis的核心。
映射文件中有很多屬性,常用的就是parameterType(輸入類型)、resultType(輸出類型)、resultMap()、rparameterMap()。
實例步驟
先建好實體類Teacher和接口類
Teacher類
package com.lanou3g.mybaties.bean; import lombok.Getter; import lombok.Setter; @Getter @Setter public class Teacher { private Integer id; private String tname; private Integer age; private Integer salary; public Teacher(){} @Override public String toString() { return "Teacher{" + "id=" + id + ", tname='" + tname + '\'' + ", salary=" + salary + ", remark='" + remark + '\'' + ", age=" + age + '}'; } private String remark; public Teacher(Integer id, String tname, Integer age, Integer salary, String remark) { this.id = id; this.tname = tname; this.age = age; this.salary = salary; this.remark = remark; } public Teacher(Integer id) { this.id = id; } }
接口類
package com.lanou3g.mybaties.dao; import com.lanou3g.mybaties.bean.Teacher; import org.apache.ibatis.annotations.Param; import java.util.List; public interface TeacherDao { List<Teacher> queryAll(); Teacher queryById(int id); Teacher queryByIdAndAge(@Param("id") int id, @Param("age") int age); int insertTeacher(Teacher teacher); int insertTeacherByParam(@Param("tname") String tname, @Param("age") int age); int updateTeacherById(Teacher teacher); int deleteTeacherById(int id); }
主要還是xml配置文件的配置,下面是 mybatis_conf.xml文件,它主要引入外部的properties文件(用於配置數據源)、定義類型別名(全局)、配置多套環境的數據庫連接參數及引入哪些Mapper映射文件等
mybatis_conf.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="jdbc.properties" /> <settings> <setting name="mapUnderscoreToCamelCase" value="false"/> </settings> <typeAliases> <!--這樣我們就可以在mybatis的的上下文中使用Teacher來代替全路徑名瞭,減少配置的復雜度。 --> <typeAlias type="com.lanou3g.mybaties.bean.Teacher" alias="Teacher" /> <!--default屬性,這個屬性作用就是指定當前情況下使用哪個數據庫配置, 也就是使用哪個<environment>節點的配置, default的值就是配置的<environment>標簽元素的id值。--> <environments default="test"> <environment id="test"> <!-- 事務管理器: MANAGED: 這個配置就是告訴mybatis不要幹預事務,具體行為依賴於容器本身的事務處理邏輯。 JDBC: 這個配置就是直接使用瞭 JDBC 的提交和回滾設置,它依賴於從數據源得到的連接來管理事務作用域。 --> <!-- 使用jdbc事務管理 --> <transactionManager type="JDBC"/> <!-- 數據庫連接池 --> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <!--<mappers>用來在mybatis初始化的時候,告訴mybatis需要引入哪些Mapper映射文件。--> <mappers> <!--通過class屬性指定mapper接口名稱,此時對應的映射文件必須與接口位於同一路徑下, 並且名稱相同--> <!--通過resource屬性引入classpath路徑的相對資源--> <mapper resource="mapper/TeacherMapper.xml" /> </mappers> </configuration>
TeacherMapper.xml映射文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace對應空Dao接口的全名 --> <!--namespace屬性 在MyBatis中,Mapper中的namespace用於綁定Dao接口的,即面向接口編程。 它的好處在於當使用瞭namespace之後就可以不用寫接口實現類, 業務邏輯會直接通過這個綁定尋找到相對應的SQL語句進行對應的數據處理--> <mapper namespace="com.lanou3g.mybaties.dao.TeacherDao"> <!-- 此處的id是查詢語句的名稱,對應接口中的方法名 --> <!--指定 resultType 返回值類型時 Teacher 類型的, Teacher 在這裡是一個別名,代表的是 com.lanou3g.mybaties.bean.Teacher 對於引用數據類型,都是將大寫字母轉小寫,比如 HashMap 對應的別名是 'hashmap' 基本數據類型考慮到重復的問題,會在其前面加上 '_',比如 byte 對應的別名是 '_byte' --> <select id="queryAll" resultType="Teacher"> <!-- 通過 resultType 指定查詢的結果是 Teacher 類型的數據 隻需要指定 resultType 的類型,MyBatis 會自動將查詢的結果映射成 JavaBean 中的屬性 --> select * from teacher; </select> <!-- 帶一個簡單類型的參數, 這種情況下parameterType屬性可以省略, mybatis可以自動推斷出類型 --> <select id="queryById" resultType="Teacher"> select * from teacher where id = #{id}; </select> <!-- 帶兩個參數,需要在接口中通過@Param註解指定名稱(因為編譯時參數名不會保留) --> <select id="queryByIdAndAge" resultType="Teacher"> select * from teacher where id = #{id} and age <= #{age}; </select> <!-- insert、update、delete的返回值都是int(影響行數) --> <!-- 自定義類型參數,通過#{屬性名}可以直接獲取引入類型參數的屬性值 --> <insert id="insertTeacher" parameterType="Teacher"> insert into teacher(tname) values (#{tname}); </insert> <insert id="insertTeacherByParam"> insert into teacher(tname, age) values (#{tname}, #{age}); </insert> <update id="updateTeacherById" parameterType="Teacher"> update teacher set tname = #{tname}, age = #{age} where id = #{id} </update> <delete id="deleteTeacherById"> delete from teacher where id = #{id}; </delete> </mapper>
前面完成後,就要測試瞭。不過在測試時需要實例化創建對象,因要實現多個方法,在這先建MyBatisTools.java工具類,其主要進行封裝Mybatis初始化操作,要求支持創建多env sqlSessionFactory,整個應用生命周期內相同env的sqlSessionFactory對象隻有一個(這個類不要過於探究,會用即可)。
MyBatisTools.java
package com.lanou3g.mybaties; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.InputStream; import java.util.concurrent.ConcurrentHashMap; /** * 封裝Mybatis初始化操作 * 支持創建多env sqlSessionFactory * 整個應用生命周期內相同env的sqlSessionFactory對象隻有一個 */ @Slf4j public class MyBatisTools { private static ConcurrentHashMap<String, SqlSessionFactory> factoryMap = new MyConcurrentHashMap(); private static MyBatisTools myBatisTools; private MyBatisTools() {} public static MyBatisTools getInstance() { if(myBatisTools == null) { synchronized (MyBatisTools.class) { if(myBatisTools == null) { myBatisTools = new MyBatisTools(); } } } log.debug("當前一共有: " + factoryMap.size() +"個SqlSessionFactory實例"); log.debug("他們分別是: " + factoryMap); return myBatisTools; } public SqlSessionFactory getSessionFactory(String env) { try { // 1. 讀入配置文件 InputStream in = Resources.getResourceAsStream("mybatis_conf.xml"); // 2. 構建SqlSessionFactory(用於獲取sqlSession) SqlSessionFactory sessionFactory = null; synchronized (factoryMap) { if(factoryMap.containsKey(env)) { return factoryMap.get(env); } else { sessionFactory = new SqlSessionFactoryBuilder().build(in, env); factoryMap.put(env, sessionFactory); } } return sessionFactory; } catch (Exception e) { log.error("初始化SqlSessionFactory失敗", e); return null; } } public SqlSession openSession() { return getSessionFactory(null).openSession(); } public SqlSession openSession(boolean autoCommit) { return getSessionFactory(null).openSession(autoCommit); } public SqlSession openSession(ExecutorType executorType, boolean autoCommit) { return getSessionFactory(null).openSession(executorType, autoCommit); } } /** * 繼承原生ConcurrentHashMap,處理null key問題 */ class MyConcurrentHashMap extends ConcurrentHashMap { @Override public Object put(Object key, Object value) { if(key == null) { key = "null"; } return super.put(key, value); } @Override public boolean containsKey(Object key) { if(key == null) { key = "null"; } return super.containsKey(key); } @Override public Object get(Object key) { if(key == null) { key = "null"; } return super.get(key); } }
最後就是測試,在AppTest類測試
AppTest
@Slf4j public class AppTest { /** * Rigorous Test :-) */ TeacherDao teacherDao = null; @Before public void setUp() { teacherDao = MyBatisTools.getInstance().openSession(true).getMapper(TeacherDao.class); } /** * 練習查詢多個庫(用到瞭多環境配置) */ @Test public void textqueryById(){ Teacher teacher=teacherDao.queryById(1); System.out.println(teacher); } @Test public void text(){ List<Teacher> teachers=teacherDao.queryAll(); System.out.println(teachers); } /** * 多個參數查詢語句 */ @Test public void testQueryByIdAndAge() { Teacher teacherList = teacherDao.queryByIdAndAge(1, 65); log.info("查詢結果:" + teacherList); } @Test public void testInsert() { // 新增Teacher表 System.out.println("--------------插入前:"); List<Teacher> teacherList = teacherDao.queryAll(); System.out.println(teacherList); int ret = teacherDao.insertTeacher(new Teacher("好人")); log.info("影響的行數: " + ret); // 比較low的寫法(不推薦) //int ret = teacherDao.insertTeacherByParam("哈哈哥", 99); //log.info("影響的行數: " + ret); System.out.println("--------------插入後:"); teacherList = teacherDao.queryAll(); System.out.println(teacherList); } @Test public void testUpdate() { Teacher teacher = new Teacher(); teacher.setId(6); teacher.setAge(99); teacher.setTname("喬巴老師"); int rows = teacherDao.updateTeacherById(teacher); log.info("更新行數:" + rows); } @Test public void testDelete() { int rows = teacherDao.deleteTeacherById(13); log.info("刪除行數:" + rows); } @Test public void testinsertTeacherByParam(){ int row=teacherDao.insertTeacherByParam("hh",22); System.out.println(row); } }
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。如有錯誤或未考慮完全的地方,望不吝賜教。
推薦閱讀:
- 在MyBatis中實現一對多查詢和多對一查詢的方式詳解(各兩種方式)
- Mybatis配置映射文件中parameterType的用法講解
- Mybatis中ResultMap解決屬性名和數據庫字段名不一致問題
- MyBatis配置的應用與對比jdbc的優勢
- Mybatis結果集映射一對多簡單入門教程