前言?
在MySQL的學習階段,我們知道了如何使用JDBC去操作,也正是因為學習了JDBC也知道其操作的繁瑣,每次的CRUD操作都需要從數據庫連接池中去獲取數據庫連接,然后再編寫SQL語句,并綁定對應的參數,接著通過連接執行SQL語句,如果返回值的話還得處理返回值,最后還得釋放連接等資源。明明在增刪改查階段只需要修改SQL語句以及對應的參數和處理返回結果即可,但還是存在很多重復性的操作的。因此,Mybatis就由此而誕生了,Mybatis實現了對JDBC的進一步簡化與封裝,讓Java開發者可以更加簡單的去操作數據庫。
Mybatis快速入門
Mybatis操作數據庫的步驟
1、準備工作(創建SpringBoot項目、數據庫表的準備、Java實體類的準備)
2、引入Mybatis的相關依賴,配置Mybatis(數據庫連接信息)
3、編寫SQL語句(使用注解或者xml)
4、測試代碼
File -> New -> Project:
接下來創建對應的數據表以及填充數據:
-- 創建表[用戶表]
DROP TABLE IF EXISTS userinfo;
CREATE TABLE `userinfo` (`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,`username` VARCHAR ( 127 ) NOT NULL,`password` VARCHAR ( 127 ) NOT NULL,`age` TINYINT ( 4 ) NOT NULL,`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-女 0-默認',`phone` VARCHAR ( 15 ) DEFAULT NULL,`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-刪除',`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now(),PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4; -- 添加用戶信息
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
創建Java中對應的實體類:
@Data
public class UserInfo {private Integer id;private String username;private String password;private Integer age;private Integer gender;private String phone;private Integer deleteFlag;private Date createTime;private Date updateTime;
}
配置數據庫連接:
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=falseusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
接下來就是編寫查詢SQL了(先使用看看效果,后面再學習)
@Mapper // 只能使用@Mapper注解
public interface UserInfoMapper { // 在mapper包下(屬于數據層)@Select("select * from userinfo")List<UserInfo> selectAll();
}
接著在UserInfoMapper這個類所在的文件中,右鍵找到Generate,點擊 Test?
按照上述步驟,就會生成一個測試類:
@SpringBootTest // 加載spring的運行環境(為了DI)
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@Test// 標記該方法為測試方法,可以直接運行void selectAll() {List<UserInfo> userInfos = userInfoMapper.selectAll();System.out.println(userInfos);}
}
?我們只需要在@Test注解的左側點擊run即可運行該方法(運行結果如下)
從上述步驟來看,從編寫SQL語句到測試的步驟非常簡單。 所以的一切都是spring幫我們做好了,我們只需要使用即可。
注意:
1、只有查詢的SQL中對應的字段才會在對象對應的屬性中有值。但細心的小伙伴可能會發現,刪除標志、創建時間、更新時間 也同樣沒有值,這個我們后面再學習。
2、Mybatis也是一個框架,是獨立于Spring框架的,Mybatis是對JDBC的封裝,即JDBC在Maven項目中可以使用,同樣Mybatis也能在Maven項目中使用。但要注意的是上面的依賴是只能在SpringBoot項目中使用,在Maven項目使用的依賴并不是長那樣。
既然已經知道了Mybatis框架的優點,接下來就具體學習Mybatis是如何操作數據庫的。
Mybatis的基礎操作(注解實現接口類)
使用Mybatis操作數據庫的話,我們都是創建一個mapper包用來存放具體的操作接口,然后在對應的接口中去編寫方法。接著就只需要Mybatis來實現該接口方法,然后我們再將該接口類注冊到Spring的容器中,使用容器來實現依賴注入,接著就是調用對應的操作方法即可。 Mybatis 實現 該接口方法的方式有兩種:1、使用注解;2、使用xml。我們先來學習簡單的注解。
打印日志
在Mybatis中,我們可以使用日志,來查看SQL語句的執行、參數的傳遞,以及執行的結果。因此我們需要先配置:
# 配置Mybatis日志
mybatis:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
查詢
如果我們想要查詢數據庫中的數據,只需要創建一個接口方法,并在上方加上@Select注解,里面加上查詢的語句即可。
需求:查詢id=1的用戶
@Mapper // 只能使用@Mapper注解
public interface UserInfoMapper { // 在mapper包下(屬于數據層)@Select("select * from userinfo")List<UserInfo> selectAll();@Select("select * from userInfo where id=1")UserInfo selectById();}
測試類?
@SpringBootTest // 加載spring的運行環境(為了DI)
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@Test// 標記該方法為測試方法,可以直接運行void selectAll() {List<UserInfo> userInfos = userInfoMapper.selectAll();System.out.println(userInfos);}@Testvoid selectById() {UserInfo Uer1=userInfoMapper.selectById();}
}
結果
雖然上述方式能夠查詢id = 1的數據,但是這種方法是寫死的,即通過手動指定來查詢的數據,但在實際開發中,往往是需要動態的數值。這里就需要用到 "#{參數名}" 的方式了。
@Select("select * from userInfo where id=#{id}")UserInfo selectById2(Integer id);
測試類?
void selectById2() {UserInfo Uer2=userInfoMapper.selectById2(2);System.out.println(Uer2);}
結果
注意:
1、如果接口方法里面的形參只有一個時,#{參數名},這里的參數名可以是任意值。
2、如果想改名的話,需要通過@Param注解將修改后的參數名寫入其中。

解決方法:
@Select("select id, username, `password`, age, gender, phone, delete_flag as
deleteFlag, " +"create_time as createTime, update_time as updateTime from user_info")
public List<UserInfo> queryAllUser();
@Select("select id, username, `password`, age, gender, phone, delete_flag,
create_time, update_time from user_info")
@Results({@Result(column = "delete_flag",property = "deleteFlag"),@Result(column = "create_time",property = "createTime"),@Result(column = "update_time",property = "updateTime")
})
List<UserInfo> queryAllUser();
@Select("select id, username, `password`, age, gender, phone, delete_flag,
create_time, update_time from user_info")
@Results(id = "resultMap",value = {@Result(column = "delete_flag",property = "deleteFlag"),@Result(column = "create_time",property = "createTime"),@Result(column = "update_time",property = "updateTime")
})
List<UserInfo> queryAllUser();
@Select("select id, username, `password`, age, gender, phone, delete_flag,
create_time, update_time " +"from user_info where id= #{userid} ")
@ResultMap(value = "resultMap")
UserInfo queryById(@Param("userid") Integer id);
?開啟駝峰命名(推薦)
mybatis:configuration:map-underscore-to-camel-case: true #配置駝峰?動轉換
駝峰命名規則: abc_xyz => abcXyz? 表中字段名:abc_xyz? 類中屬性名:abcXyz
?增加
增加數據,是通過@Insert注解實現的,只需要在里面加上對應的SQL語句即可。?
上述這種方式是比較繁瑣的,我們可以使用傳輸對象的方式:
Insert語句默認返回的是受影響的行數,但在有些情景下,數據插入之后,需要獲取的新插入的數據的id,提供給后續的操作。例如,訂單系統的中的下完訂單之后,后續的物流系統、庫存系統等都需要拿到訂單的id來進行操作。如果想要拿到自增的id,需要在接口方法上添加Options注解。
獲取自動生成的主鍵值:通過Options注解來獲取,其屬性useGeneratedKeys=true表示使用數據庫自增主鍵,keyColumn用于指定數據庫table中的主鍵,keyProperty用于指定傳入對象的屬性(綁定到對象的哪個屬性)。如果我們已經在數據庫表中指定了主鍵,并且數據庫中主鍵的名稱和Java屬性的名稱是相同的,那么keyColumn屬性可以省略。
@Options(useGeneratedKeys = true, keyProperty = "id")@Insert("insert into userinfo (username, password, age, gender) values (#{username},#{password},#{age},#{gender})")Integer insert(UserInfo userInfo);
測試代碼
@Testvoid insert() {UserInfo userInfo = new UserInfo();userInfo.setUsername("zhaoliu");userInfo.setPassword("zhaoliu");userInfo.setAge(18);userInfo.setGender(1);Integer result = userInfoMapper.insert(userInfo);System.out.println("result: "+result + ",id:"+userInfo.getId());}
?刪除
@Delete("delete from userinfo where id = #{id}")Integer delete(Integer id);
更新
更新數據,是通過@Update注解實現的,只需要在里面加上對應的SQL語句即可。??
@Update("update userInfo set username = #{username} where id = #{id}")Integer update(UserInfo userInfo);
void update() {UserInfo userInfo=new UserInfo();userInfo.setUsername("lisi");userInfoMapper.selectById2(2);Integer result=userInfoMapper.update(userInfo);System.out.println(result);}
XML實現接口類?
快速上手?
配置數據源和導入依賴與前面是一樣的。下面是不一樣的配置:
1、配置 Mybatis 的 xml文件路徑
mybatis: # classpath 表示resources目錄下,整體表示的是 resources/mapper/所有xml文件# * 代表通配符,表示只要是 .xml 文件即可mapper-locations: classpath:mapper/*.xml
第二步同樣還是創建接口類,第三步不再是通過注解來實現SQL語句了,而是通過在配置的XML文件目錄下創建XML文件,在文件中編寫對應的實現SQL語句。
在新創建的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 表示的該xml文件實現的接口類的全限定名稱 -->
<mapper namespace=""></mapper>
?這里的namespace的值就是UserInfoMapperXML文件的全限定類名。
例如:com.example.demo.UserInfoMapperXML
這里再介紹一個插件: mybatisx,
安裝該插件之后,會有兩個好處:
1、知道XML文件中的mapper標簽的namespace值是否綁定正確。如果綁定正確的話,對應的文件位置就會出現小鳥圖標,點擊該圖標可以在綁定的文件之中任意跳轉。?
?2、插件會自動校驗對應的接口類的接口方法是否生成了對應的實現標簽。如果生成對應的實現標簽的話,就會在對應的標簽和方法之間生成小鳥圖標,同樣可以實現自動跳轉,反之,如果沒有生成的話,就會在對應的接口類所在的方法上爆紅。
接下來,就可以在標簽中編寫對應的SQL語句了。
<select id="select" resultType="com.example.demo.UserInfo">select * from userinfo;</select>
?最后就是測試對應的SQL語句了,同樣在接口類中,鼠標右鍵 -> Generate -> Test:
查詢?
根據id查詢:
System.out.println(userInfoMapperXML.select());
<select id="selectbyid" resultType="com.example.demo.UserInfo">select * from userinfo where id = #{id};</select>
@Testvoid selectbyid() {System.out.println(userInfoMapperXML.selectbyid(3));}
根據 username 和 password 查詢:
// 根據username和password查詢
UserInfo selectByUsernameAndPassword(String username, String password);@Test
void selectByUsernameAndPassword() {System.out.println(userInfoMapperXML.selectByUsernameAndPassword("admin", "admin_123456"));
}
根據 對象 來查詢:
// 根據對象來查詢
UserInfo selectByUserInfo(UserInfo userInfo);<select id="selectByUserInfo" resultType="com.springboot.mybatisdemo2.pojo.UserInfo"><!-- 注意,這里#{}內部的是實體類的屬性名,其余的都是數據庫的字段 -->select * from user_info where username = #{username} and password = #{password}
</select>@Test
void selectByUserInfo() {UserInfo userInfo = new UserInfo();userInfo.setUsername("admin");userInfo.setPassword("admin_123456");System.out.println(userInfoMapperXML.selectByUserInfo(userInfo));
}
由于之前在配置文件中,設置過了駝峰自動轉換,因此這里并不會出現對象屬性為null的情況。現在我們來看在XML文件中,使用起別名和result標簽如何解決。
<!-- 起別名 -->
<select id="selectAll2" resultType="com.springboot.mybatisdemo2.pojo.UserInfo">select id, username, password, age, gender, phone,delete_flag as deleteFlag,create_time as createTime,update_time as updateTime from user_info
</select><!-- id是給別的標簽使用所引用的 type表示映射到哪個實體類上 -->
<resultMap id="resultMap" type="com.springboot.mybatisdemo2.pojo.UserInfo"><!-- 規范的寫法是要將數據表中所有字段和實體類的屬性相對應的,即使有些字段和屬性是對應上的,也最好要加上,因為是規范id 表示主鍵,result表示普通字段--><id column="id" property="id"/><result column="username" property="username"/><result column="password" property="password"/><result column="age" property="age"/><result column="gender" property="gender"/><result column="phone" property="phone"/><result column="delete_flag" property="deleteFlag"/><result column="create_time" property="createTime"/><result column="update_time" property="updateTime"/>
</resultMap><!-- resultMap表示要引用的映射關系 -->
<select id="selectAll3" resultType="com.springboot.mybatisdemo2.pojo.UserInfo" resultMap="resultMap">select * from user_info
</select>
增加
新增數據,傳遞參數:
// 新增數據:傳遞參數
Integer insertByUsernameAndPassword(String username, String password);<insert id="insertByUsernameAndPassword">insert into user_info (username, password) values (#{username}, #{password})
</insert>@Test
void insertByUsernameAndPassword() {System.out.println(userInfoMapperXML.insertByUsernameAndPassword("444", "444"));
}
新增數據,傳遞對象:
// 新增數據:傳遞對象
Integer insertByUserInfo(UserInfo userInfo);<insert id="insertByUserInfo">insert into user_info (username, password) values (#{username}, #{password})
</insert>@Test
void insertByUserInfo() {UserInfo userInfo = new UserInfo();userInfo.setUsername("444");userInfo.setPassword("444");System.out.println(userInfoMapperXML.insertByUserInfo(userInfo));
}
新增數據,獲取自增id:
// 新增數據:獲取自增的id
Integer insertGetGeneratedKey(UserInfo userInfo);<insert id="insertGetGeneratedKey" useGeneratedKeys="true" keyProperty="id">insert into user_info (username, password) values (#{username}, #{password})
</insert>@Test
void insertGetGeneratedKey() {UserInfo userInfo = new UserInfo();userInfo.setUsername("444");userInfo.setPassword("444");System.out.println("受影響的行數: " + userInfoMapperXML.insertGetGeneratedKey(userInfo));System.out.println("獲取自增的id: " + userInfo.getId());
}
刪除
刪除數據,傳遞id:
// 刪除數據:傳遞參數
Integer deleteById(Integer id);<delete id="deleteById">delete from user_info where id = #{id}
</delete>@Test
void deleteById() {System.out.println(userInfoMapperXML.deleteById(40));
}
刪除數據,傳遞對象:?
// 刪除數據:傳遞對象
Integer deleteByUserInfo(UserInfo userInfo);<delete id="deleteByUserInfo">delete from user_info where username = #{username} and password = #{password}
</delete>@Test
void deleteByUserInfo() {UserInfo userInfo = new UserInfo();userInfo.setUsername("admin");userInfo.setPassword("admin");System.out.println(userInfoMapperXML.deleteByUserInfo(userInfo));
}
更新
更新數據,傳遞參數?
// 更新數據:傳遞參數
Integer updateByUsernameAndPassword(Long id, String username, String password);<update id="updateByUsernameAndPassword">update user_info set username = #{username}, password = #{password} where id = #{id}
</update>@Test
void updateByUsernameAndPassword() {System.out.println(userInfoMapperXML.updateByUsernameAndPassword(33 L, "admin", "admin"));
}
多表查詢
由于實際開發中,多表查詢的使用頻率不是很高,因此這里不再演示了。 感興趣可以看看別的博主文章
#{} 和 ${} 的區別
1 @Select("select username, `password`, age, gender, phone from user_info where
id= #{id} ")
2 UserInfo queryById(Integer id);
1 select username, `password`, age, gender, phone from user_info where id= ?
@Select("select username, `password`, age, gender, phone from user_info where id= ${id} ") 2 UserInfo queryById(Integer id);
可以看到, 這次的參數是直接拼接在SQL語句中了
@Select("select username, `password`, age, gender, phone from user_info where
username= #{name} ")
UserInfo queryByName(String name);
@Select("select username, `password`, age, gender, phone from user_info where
username= ${name} ")
UserInfo queryByName(String name);

@Select("select username, `password`, age, gender, phone from user_info where
username= '${name}' ")
UserInfo queryByName(String name);

從上?兩個例?可以看出:#{} 使?的是預編譯SQL, 通過 ? 占位的?式, 提前對SQL進?編譯, 然后把參數填充到SQL語句中. #{} 會根據參數類型, ?動拼接引號 '' .${} 會直接進?字符替換, ?起對SQL進?編譯. 如果參數為字符串, 需要加上引號 '' .參數為數字類型時, 也可以加上, 查詢結果不變, 但是可能會導致索引失效, 性能下降.
預編譯SQL,編譯?次之后會將編譯后的SQL語句緩存起來,后?再次執?這條語句時,不會再次編譯(只是輸?的參數不同), 省去了解析優化等過程, 以此來提?效率
@Select("select username, `password`, age, gender, phone from user_info where
username= '${name}' ")
List<UserInfo> queryByName(String name);
@Test
void queryByName() {List<UserInfo> userInfos = userInfoMapper.queryByName("admin");System.out.println(userInfos);
}
?
1 @Test
2 void queryByName() {
3 List<UserInfo> userInfos = userInfoMapper.queryByName("' or 1='1");
4 System.out.println(userInfos);
5 }

可以看出來, 查詢的數據并不是??想要的數據. 所以?于查詢的字段,盡量使? #{} 預查詢的?式SQL注?是?種?常常?的數據庫攻擊?段, SQL注?漏洞也是?絡世界中最普遍的漏洞之?.如果發?在??登錄的場景中, 密碼輸?為 ' or 1='1 , 就可能完成登錄

@Select("select id, username, age, gender, phone, delete_flag, create_time,
update_time " +"from user_info order by id ${sort} ")
List<UserInfo> queryAllUserBySort(String sort);
使? ${sort} 可以實現排序查詢, ?使? #{sort} 就不能實現排序查詢了.注意: 此處 sort 參數為String類型, 但是SQL語句中, 排序規則是不需要加引號 '' 的, 所以此時的 ${sort} 也不加引號

@Select("select id, username, age, gender, phone, delete_flag, create_time,
update_time " +"from user_info where username like '%#{key}%' ")
List<UserInfo> queryAllUserByLike(String key);
@Select("select id, username, age, gender, phone, delete_flag, create_time,
update_time " +"from user_info where username like concat('%',#{key},'%')")
List<UserInfo> queryAllUserByLike(String key);
數據庫連接池?
我們前面在學習JDBC時,每次增刪改查操作都需要去手動創建連接,當SQL語句執行完畢之后,就需要去手動釋放連接,整個過程是比較消耗資源的,特別是頻繁創建與銷毀連接時。前面學習多線程時,也是需要手動創建新線程,后來學習了如何使用線程池之后,就可以直接向線程池中拿線程,使用完之后,繼續放到線程池中,這樣的做法就是避免了頻繁創建與銷毀帶來的時間開銷。同樣,數據庫連接也可以存放到數據庫連接池中,SpringBoot項目也是封裝了數據庫連接池:

<!-- springboot3.x版本 -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-3-starter</artifactId><version>1.2.21</version>
</dependency><!-- springboot2.x版本 -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.17</version>
</dependency>
再次啟動測試用例,觀察控制臺的輸出結果: