前言
一直在使用,從未系統的總結起來。所以這里給大家帶來的是mybatis的總結,系統大家能夠對這個框架有一定的系統的學習與認識。
mybatis和Hibernate應該是現在主流的ORM框架了。
mybatis學習的步驟:
mybatis入門 --> 全局配置文件和映射文件詳解 --> 高級映射(一對一,一對多,多對多) -->延遲加載機制 -->一級緩存,
二級緩存(整合ehcache) --> spring整合mybatis --> 逆向工程
一、Mybatis簡介
- Mybatis是Apache的一個Java開源項目,是一個支持動態Sql語句的持久層框架。Mybatis可以將Sql語句配置在XML文件中,避免
將Sql語句硬編碼在Java類中。 - MyBatis 是支持普通 SQL查詢,存儲過程和高級映射的優秀持久層框架。MyBatis 消除了幾乎所有的JDBC代碼和參數的手工設置
以及結果集的檢索。MyBatis 使用簡單的 XML或注解用於配置和原始映射,將接口和 Java 的POJOs(Plain Ordinary Java
Objects,普通的 Java對象)映射成數據庫中的記錄。
總結一下:
- 1)支持普通sql查詢
- 2)高級映射
- 3)存儲過程
- 4)消除了幾乎所有jdbc代碼和參數的手工設置以及結果集的檢索
二、Mybatis與JDBC、Hibernate的區別
2.1 Mybatis與JDBC的區別
- Mybatis通過參數映射方式,可以將參數靈活的配置在SQL語句中的配置文件中,避免在Java類中配置參數(JDBC)
- Mybatis通過輸出映射機制,將結果集的檢索自動映射成相應的Java對象,避免對結果集手工檢索(JDBC)
- Mybatis可以通過Xml配置文件對數據庫連接進行管理。
三、感受JDBC和Mybatis
MyBatis 是來和數據庫打交道。那么在這之前,我們是使用 JDBC 來對數據庫進行增刪改查等一系列操作的,而我們之所以會放棄
使用 JDBC,轉而使用 MyBatis 框架,這是為什么呢?
為什么使用Mybatis比JDBC直接連接方便和性能上好很多呢?
- 新建Person實體類
public class Person {
private Long pid;
private String pname;
public Long getPid() {
return pid;
}
public void setPid(Long pid) {
this.pid = pid;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
}
- JDBC 查詢操作
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import javax.swing.DebugGraphics;
import com.ys.bean.Person;
public class CRUDDao {
//MySQL數據庫驅動
public static String driverClass = "com.mysql.jdbc.Driver";
//MySQL用戶名
public static String userName = "root";
//MySQL密碼
public static String passWord = "654321";
//MySQL URL
public static String url = "jdbc:mysql://localhost:3306/study";
//定義數據庫連接
public static Connection conn = null;
//定義聲明數據庫語句,使用 預編譯聲明 PreparedStatement提高數據庫執行性能
public static PreparedStatement ps = null;
//定義返回結果集
public static ResultSet rs = null;
/**
* 查詢 person 表信息
* @return:返回 person 的 list 集合
*/
public static List<Person> readPerson(){
List<Person> list = new ArrayList<>();
try {
//加載數據庫驅動
Class.forName(driverClass);
//獲取數據庫連接
conn = DriverManager.getConnection(url, userName, passWord);
//定義 sql 語句,?表示占位符
String sql = "select * from person where pname=?";
//獲取預編譯處理的statement
ps = conn.prepareStatement(sql);
//設置sql語句中的參數,第一個為sql語句中的參數的?(從1開始),第二個為設置的參數值
ps.setString(1, "qzy");
//向數據庫發出 sql 語句查詢,並返回結果集
rs = ps.executeQuery();
while (rs.next()) {
Person p = new Person();
p.setPid(rs.getLong(1));
p.setPname(rs.getString(2));
list.add(p);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//關閉數據庫連接
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return list;
}
public static void main(String[] args) {
System.out.println(CRUDDao.readPerson());
}
}
- 分析問題
1)問題一:數據庫連接,使用時就創建,使用完畢就關閉,這樣會對數據庫進行頻繁的獲取連接和關閉連接,造成數據庫資源浪費,
影響數據庫性能。
設想解決:使用數據庫連接池管理數據庫連接
2)問題二:將 sql 語句硬編碼到程序中,如果sql語句修改了,那么需要重新編譯 Java 代碼,不利於系統維護
設想解決:將 sql 語句配置到 xml 文件中,即使 sql 語句變化了,我們也不需要對 Java 代碼進行修改,重新編譯
3)問題三:在 PreparedStatement 中設置參數,對占位符設置值都是硬編碼在Java代碼中,不利於系統維護
設想解決:將 sql 語句以及占位符和參數都配置到 xml 文件中
4)問題四:從 resultset 中遍歷結果集時,對表的字段存在硬編碼,不利於系統維護
設想解決:將查詢的結果集自動映射為 Java 對象
5)問題五:重復性代碼特別多,頻繁的 try-catch
設想解決:將其整合到一個 try-catch 代碼塊中
6)問題六:緩存做的很差,如果存在數據量很大的情況下,這種方式性能特別低
設想解決:集成緩存框架去操作數據庫
7)問題七:sql 的移植性不好,如果換個數據庫,那么sql 語句可能要重寫
設想解決:在 JDBC 和 數據庫之間插入第三方框架,用第三方去生成 sql 語句,屏蔽數據庫的差異
四、基於XML的Mybatis入門實例
4.1 創建數據庫表
創建完成,隨便寫幾條數據進去。
4.2 創建一個Java項目,並導入相應的jar包
備注:log4j和Junit不是必須的,但是我們為了查看日志以及便於測試,加入了這兩個jar包
目錄結構:
4.3 項目中添加數據庫配置文件 mybatis-configuration.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>
<!-- 注意:environments標簽,當mybatis和spring整合之后,這個標簽是不用配置的 -->
<!-- 可以配置多個運行環境,但是每個 SqlSessionFactory 實例只能選擇一個運行環境
一、development:開發模式
二、work:工作模式-->
<environments default="development">
<!--id屬性必須和上面的default一樣 -->
<environment id="development">
<!--事務管理器
一、JDBC:這個配置直接簡單使用了 JDBC 的提交和回滾設置。它依賴於從數據源得到的連接來管理事務范圍
二、MANAGED:這個配置幾乎沒做什么。它從來不提交或回滾一個連接。而它會讓容器來管理事務的整個生命周期
比如 spring 或 JEE 應用服務器的上下文,默認情況下,它會關閉連接。然而一些容器並不希望這樣,
因此如果你需要從連接中停止它,就可以將 closeConnection 屬性設置為 false,比如:
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
-->
<transactionManager type="JDBC"/>
<!--dataSource 元素使用標准的 JDBC 數據源接口來配置 JDBC 連接對象源 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/study"/>
<property name="username" value="root"/>
<property name="password" value="654321"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 注冊userMapper.xml文件,
userMapper.xml位於com.lance.mybatis這個包下,所以resource寫成com/lance/mybatis/userMapper.xml-->
<mapper resource="com/lance/mybatis/mapper/userMapper.xml"/>
</mappers>
</configuration>
4.4 定義表對應的實體類
package com.lance.mybatis.bean;
import java.util.Date;
public class User {
private int id;
private String username;
private int password;
private String sex;
private Date birthday;
private String address;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getPassword() {
return password;
}
public void setPassword(int password) {
this.password = password;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password=" + password +
", sex='" + sex + '\'' +
", birthday=" + birthday +
", address='" + address + '\'' +
'}';
}
}
4.5 定義操作 user 表的sql映射文件userMapper.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">
<mapper namespace="com.lance.mybatis.mapper.userMapper">
<!-- 根據 id 查詢 user 表中的數據
id:唯一標識符,此文件中的id值不能重復
resultType:返回值類型,一條數據庫記錄也就對應實體類的一個對象
parameterType:參數類型,也就是查詢條件的類型
-->
<select id="selectUserById"
resultType="com.lance.mybatis.bean.User" parameterType="int">
<!-- 這里和普通的sql 查詢語句差不多,后面的 #{id}表示占位符,里面不一定要寫id,寫啥都可以,但是不要空着 -->
select * from user where id = #{id}
</select>
<!-- 查詢 user 表的所有數據
注意:因為是查詢所有數據,所以返回的應該是一個集合,這個集合里面每個元素都是User類型
-->
<select id="selectUserAll" resultType="com.lance.mybatis.bean.User">
select * from user
</select>
<!-- 模糊查詢:根據 user 表的username字段
下面兩種寫法都可以,但是要注意
1、${value}里面必須要寫value,不然會報錯
2、${}表示拼接 sql 字符串,將接收到的參數不加任何修飾拼接在sql語句中
3、使用${}會造成 sql 注入
-->
<select id="selectLikeUserName" resultType="com.lance.mybatis.bean.User" parameterType="String">
select * from user where username like '%${value}%'
<!-- select * from user where username like #{username} -->
</select>
<!-- 向 user 表插入一條數據 -->
<insert id="insertUser" parameterType="com.lance.mybatis.bean.User">
<!-- 將插入的數據主鍵返回到 user 對象中
keyProperty:將查詢到的主鍵設置到parameterType 指定到對象的那個屬性
select LAST_INSERT_ID():查詢上一次執行insert 操作返回的主鍵id值,只適用於自增主鍵
resultType:指定 select LAST_INSERT_ID() 的結果類型
order:AFTER,相對於 select LAST_INSERT_ID()操作的順序
-->
<selectKey keyProperty="id" resultType="int" order="AFTER">
select LAST_INSERT_ID()
</selectKey>
insert into user(username,password,sex,birthday,address)
value(#{username},#{password},#{sex},#{birthday},#{address})
</insert>
<!-- 根據 id 更新 user 表的數據 -->
<update id="updateUserById" parameterType="com.lance.mybatis.bean.User">
update user set username=#{username} where id=#{id}
</update>
<!-- 根據 id 刪除 user 表的數據 -->
<delete id="deleteUserById" parameterType="int">
delete from user where id=#{id}
</delete>
</mapper>
4.6 向 mybatis-configuration.xml 配置文件中注冊 userMapper.xml 文件
4.7 測試功能類
package com.lance.mybatis.test;
import com.lance.mybatis.bean.User;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
/**
* @author lance(ZYH)
* @function
* @date 2018-07-07 21:10
*/
public class MybatisTest {
SqlSession session = null;
@Before
public void init() {
//定義mybatis全局配置文件
String resource = "com/lance/mybatis/config/mybatis-configuration.xml";
//加載 mybatis 全局配置文件
InputStream inputStream = MybatisTest.class.getClassLoader()
.getResourceAsStream(resource);
//構建sqlSession的工廠
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//根據 sqlSessionFactory 產生 session
session = sessionFactory.openSession();
}
//根據id查詢user表數據
@Test
public void getUserById() {
/*這個字符串由 userMapper.xml 文件中 兩個部分構成
<mapper namespace="com.lance.mybatis.userMapper"> 的 namespace 的值
<select id="selectUserById" > id 值*/
String statement = "com.lance.mybatis.mapper.userMapper.selectUserById";
User user = session.selectOne(statement, 1);
System.out.println(user);
session.close();
}
//查詢所有user表所有數據
@Test
public void testSelectUserAll() {
String statement = "com.lance.mybatis.mapper.userMapper.selectUserAll";
List<User> listUser = session.selectList(statement);
for (User user : listUser) {
System.out.println(user);
}
session.close();
}
//模糊查詢:根據 user 表的username字段
@Test
public void testSelectLikeUserName(){
String statement = "com.lance.mybatis.mapper.userMapper.selectLikeUserName";
List<User> listUser = session.selectList(statement, "%張%");
for(User user : listUser){
System.out.println(user);
}
session.close();
}
//向 user 表中插入一條數據
@Test
public void testInsertUser(){
String statement = "com.lance.mybatis.mapper.userMapper.insertUser";
User user = new User();
user.setUsername("劉美玲");
user.setPassword(98766);
user.setSex("女");
user.setAddress("河南省啥子去");
session.insert(statement, user);
//提交插入的數據
session.commit();
session.close();
}
//根據 id 更新 user 表的數據
@Test
public void testUpdateUserById(){
String statement = "com.lance.mybatis.mapper.userMapper.updateUserById";
//如果設置的 id不存在,那么數據庫沒有數據更改
User user = new User();
user.setId(4);
user.setUsername("jim");
session.update(statement, user);
session.commit();
session.close();
}
//根據 id 刪除 user 表的數據
@Test
public void testDeleteUserById(){
String statement = "com.lance.mybatis.mapper.userMapper.deleteUserById";
session.delete(statement,4);
session.commit();
session.close();
}
}
五、總結
5.1 插入數據之后的主鍵值
1)數據庫設置主鍵自增機制
userMapper.xml 文件中定義:
<!-- 向 user 表插入一條數據 -->
<insert id="insertUser" parameterType="com.ys.po.User">
<!-- 將插入的數據主鍵返回到 user 對象中
keyProperty:將查詢到的主鍵設置到parameterType 指定到對象的那個屬性
select LAST_INSERT_ID():查詢上一次執行insert 操作返回的主鍵id值,只適用於自增主鍵
resultType:指定 select LAST_INSERT_ID() 的結果類型
order:AFTER,相對於 select LAST_INSERT_ID()操作的順序
-->
<selectKey keyProperty="id" resultType="int" order="AFTER">
select LAST_INSERT_ID()
</selectKey>
insert into user(username,password,sex,birthday,address)
value(#{username},#{password},#{sex},#{birthday},#{address})
</insert>
2)非自增主鍵機制
<!-- 向 user 表插入一條數據 -->
<insert id="insertUser" parameterType="com.ys.po.User">
<!-- 將插入的數據主鍵返回到 user 對象中
流程是:首先通過 select UUID()得到主鍵值,然后設置到 user 對象的id中,在進行 insert 操作
keyProperty:將查詢到的主鍵設置到parameterType 指定到對象的那個屬性
select UUID():得到主鍵的id值,注意這里是字符串
resultType:指定 select UUID() 的結果類型
order:BEFORE,相對於 select UUID()操作的順序
-->
<selectKey keyProperty="id" resultType="String" order="BEFORE">
select UUID()
</selectKey>
insert into user(id,username,passwor,sex,birthday,address)
value(#{id},#{username},#{password},#{sex},#{birthday},#{address})
</insert>
5.2 特殊總結
- parameterType:指定輸入參數的類型
- resultType:指定輸出結果的類型,在select中如果查詢結果是集合,那么也表示集合中每個元素的類型
- 使用#{}:表示占位符,用來接收輸入參數,類型可以是簡單類型,pojo,HashMap等等
如果接收簡單類型,#{}可以寫成 value 或者其他名稱
如果接收 pojo 對象值,通過 OGNL 讀取對象中的屬性值,即屬性.屬性.屬性...的方式獲取屬性值 - 使用${}:表示一個拼接符,會引起 sql 注入,不建議使用
用來接收輸入參數,類型可以是簡單類型,pojo,HashMap等等
如果接收簡單類型,${}里面只能是 value
如果接收 pojo 對象值,通過 OGNL 讀取對象中的屬性值,即屬性.屬性.屬性...的方式獲取屬性值