菜鳥學Mybatis之——入門,搭建過程(全局配置文件、映射文件配置),${}、#{}兩種取值方式

Mybatis

學習Mybatis時可以結合官方文檔來學習:https://mybatis.org/mybatis-3/

1.1 引入

之前我們學習的JDBC編程,寫sql語句是如果需要傳入很多個參數(?)就要一個一個的傳,毫無技術含量,這就是我們說的板磚過程。如下:

String sql = "insert into tb_document(name,clazzname,teachername,size,time,active,url) values (?,?,?,?,?,?,?)";
        try {
            PreparedStatement ps = conn.prepareStatement(sql);
            ps.setString(1,document.getName());
            ps.setString(2,document.getClazzname());
            ps.setString(3,document.getTeachername());
            ps.setLong(4,document.getSize());
            ps.setString(5,document.getTime());
            ps.setInt(6,document.getActive());
            ps.setString(7,document.getUrl());
            return ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }

還有就是我需要的是一個對象,但是查詢出來的都是一個一個的字段,需要自己封裝。如下:

ResultSet rs = statement.executeQuery("select * from tb_document");
            List<Document> list = new ArrayList<>();
            while (rs.next()) {
                Document document = new Document();
                document.setId(rs.getInt("id"));
                document.setName(rs.getString("name"));
                document.setClazzname(rs.getString("clazzname"));
                document.setTeachername(rs.getString("teachername"));
                document.setSize(rs.getInt("size"));
                document.setActive(rs.getInt("active"));
                document.setTime(rs.getString("time"));
                document.setUrl(rs.getString("url"));
                list.add(document);
            }
            return list;

上面這些過程真的很麻煩,這種現象稱爲阻抗不匹配,就比如,我手機是華爲的,需要Type-C的充電器接口,但是你給我了一個安卓的充電線,這就需要我再找一個轉換器才能給手機充上電。而我們上面那些冗餘的代碼的作用就是將對象拆爲字段或者將字段封裝爲對象,相當於轉換器。這種過程要是要我們自己手動設置就很麻煩,Mybatis就幫我們完成了上面那些冗餘的操作,以後只需要一句話就可以完成查庫等的操作。

ORM:Object Relation Mapper 對象關係映射框架。他將我們數據庫中的字段與對象做了一個關係映射。按照映射規則自動拆裝對象。ORM框架就相當於轉換器,解決阻抗不匹配問題。慢慢的大家發現ORM框架就是針對數據庫來做的,這時開發框架的人就把數據庫連接的過程也加入了這個框架中,我們使用者就不需要再寫數據庫連接的過程了。Java的ORM框架都是對JDBC的一種封裝

總結:ORM框架就是協助我們連接數據庫並且解決阻抗不匹配的問題的框架。

市面上常見的ORM框架有:

  • JPA(太過於靈活)
  • Hibernate(常用於巨型項目,很嚴謹):全自動映射ORM框架,目的是爲了消出sql,其內部自動產生SQL,但是這失去了靈活性。
  • Mybatis(由ibatis演變來的)(又靈活,又嚴謹,擴展性還高):半自動映射ORM框架,將核心步驟編寫sql交給開發人員來完成。

1.2 Mybatis搭建過程:

  1. 導入jar包(mybatis、log4j、mysql)

  2. 創建mybatis的核心(全局)配置文件mybatis-config.xml,並配置

  3. 創建映射文件XxxMapper.xml(處理實體類對象和表之間的關係),並配置

  4. 創建mapper接口,實現兩個綁定:
    (1)接口全限定名要和映射文件的namespace保持一致
    (2)接口中方法名和SQL語句的id保持一致

  5. 獲取mybatis操作數據庫的會話對象SqlSession,通過getMapper()獲取接口的動態代理實現類

  6. 測試

    核心配置文件寫的是如何連接數據庫,映射文件寫的是如何操作數據庫(sql)

接口式編程:

原生: Dao -----> DaoImpl

Mybatis: Mapper -----> XXXMapper.xml


1.2.1 mybatis-config.cml 配置文件

標籤頭,就是前兩行,是固定格式。

<?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> <!--mybatis的分標籤-->
    <environments default="dev"> <!--環境們,不特指用哪個環境就用默認的。想用哪個環境就填哪個環境的id-->
        <environment id="dev"> <!--一個環境-->
            <transactionManager type="JDBC"></transactionManager> <!--JDBC:使用JDBC原生的事務管理方式,即提交和回滾都需要手動處理-->
            <dataSource type="POOLED"> <!--數據源,POOLED:表示使用數據庫連接池-->
                <!--配置屬性(配置數據庫的各種信息)-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://localhost:3306/db_zbmanager?serverTimezone=UTC"></property>
                <property name="username" value="root"></property>
                <property name="password" value="root"></property>
            </dataSource>
        </environment>
    </environments>

    <!--引入映射文件(註冊)-->
    <mappers>
        <mapper resource="mapper/adminmapper.xml"></mapper>
    </mappers>
</configuration>Mybatis工具第一個核心知識點:SqlSessionFactory

上面引入映射文件的過程是通過mapper引入的,每個mapper文件都要引入一次。如果文件過多就過於複雜。所以可以通<package>來批量註冊,但是有一個要求,就是此種寫法需要mapper接口和mapper映射文件必須在同一個包下(eg:<package name=“com.home.mybatis.dao”/>)。

Configuration XML
  • <environments>:設置連接數據庫的環境
    • default:設置默認使用的數據庫環境(environments中有很多environment)
  • <environment>:設置某個具體的數據庫環境
  • id:數據庫環境的唯一標識
  • <typeAliases>:爲一個類型起別名
    • <typeAlias>的兩個屬性:type:Java類型,若只設置type,默認的別名就是類名,且不區分大小寫

像上面配置的那些類型,要用全類名(eg:com.home.java.bean.Employ),比較麻煩,所以可以在覈心文件中(mybatis-config.xml)配置好他的別名,通過typeAliases(這個起別名的操作意義不大)

<typeAliases>
    <typeAlias type="com.home.java.bean.Employ" alias="Employ"></typeAlias>
</typeAliases>
  • <mapper>的三個屬性:

    • resource:引用類路徑下的sql映射文件
    • url:引用網絡路徑或者磁盤路徑下的sql映射文件
    • class:引用接口:沒有映射文件,所有的sql都是利用註解寫在接口上的。(class=“com.home.mybatis.dao.EmpMapperAnnotation”)(簡單)

    推薦:比較重要的,複雜的Dao接口我們來寫sql映射文件。不重要的,簡單的Dao接口爲了開發快速可以使用註解。

  • setting設置:

    cacheEnabled :全局開啓或禁止緩存

    一級緩存在Mapper中,好處:速度快

    二級緩存在硬盤中,靠別的程序提供緩存,好處:佔用內容空間少

    lazyLoadingEnabled :懶加載,不調用sql語句,它就不會管sql語句是否正確,也不檢查mapper,連接過程。。。只有在調用的時候才檢查。好處:節省內容,壞處:速度慢(一般用於分佈查詢的延遲加載)

    autoMappingBehavior :自動映射,如果設置爲NONE就不能將數據庫的字段與對象的屬性產生映射。所以設置爲NONE沒有什麼價值。如果設置爲FULL,則必須全部映射上,如果有一個值沒有映射上,則會出問題。

    mapper映射的其實不是定義的屬性名稱,而是getset的後面的名稱(首字母變成小寫,eg:getAccount映射的是數據庫中的account字段)

1.2.2 adminmapper.xml 映射文件配置

他的作用就是將接口和之後mybatis創建的對象關聯起來,映射器就知道它創建對象就要創建哪個接口的對象。

以後所有的sql語句將會寫在下面這個文件中

<?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定義接口-->
<mapper namespace="com.home.java.AdminMapper"> <!--namespace:實現接口和映射文件綁定-->
    
    <!--下面這句話的意思是,當我們調用AdminMapper接口的queryAdminById(int id)方法時就回來調用這裏的sql語句-->
    <!--id:方法名,parameterType:參數類型,resultType:返回值類型,爲了確定是哪個類,必須是全類名-->
    <select id="queryAdminById" parameterType="int" resultType="com.home.java.Admin">
        <!--注意這裏如果返回值類型是List也還要寫成com.home.java.Admin-->
        select * from tb_admin where id = #{id}
    </select>
    
    <select id="insert" parameterType="com.home.java.Admin">
        insert into tb_admin(account,pwd) values (account}, #{pwd})
    </select>
    
    <insert id="insert" parameterType="com.home.java.Admin">
        insert into tb_admin(account,pwd) values(#{account}, #{pwd});
    </insert>
    
</mapper>
1. Mybatis獲取參數值的兩種方式:${}、#{}

${}:底層使用statement處理語句:必須使用字符串拼接的方式操作SQL,一定要注意單引號問題(一般特殊情況下才使用此方式,如:模糊查詢和批量刪除

#{}:底層使用prepareStatement處理語句:可以使用通配符操作SQL,不需要注意單引號問題

2. 不同的參數類型,${}和#{}的不同取值方式:

2.1 當傳輸參數爲單個String或基本數據類型和其包裝類

  • #{}:可以以任意的名字獲取參數值
  • {}:只能以{value}或${_parameter}獲取(因爲基本數據類型不會存在對應的屬性,更不會存在屬性的get方法)

2.2 當傳輸參數爲JavaBean時

  • #{}和{}都可以通過屬性名直接獲取屬性值,但是要注意{}的單引號問題

2.3當傳輸多個參數時,mybatis會默認將這些參數放在map集合中

兩種方式:

  • key爲0,1,2,···,N-1,以參數爲value

  • key爲param1,param2,···。paramN,以參數爲value

  • eg:

#{}:#{0}、#{1};#{param1}、#{param2}

{}:{param1}、param2{param2},但是要注意{}的單引號問題。${}裏面可以放表達式

2.4 當傳輸Map參數時

  • #{}和{}都可以通過鍵的名字直接獲取值,但是要注意{}的單引號問題

2.5 命名參數

  • 可以通過@param(“key”)爲map集合指定鍵的名字,通過#{指定的key}取出對應的參數值

eg:Emp getEmpByParam(@Param("eid")String eid, @Param("ename")String ename);(這時就不需要我們自己手動把它放到map中了,param會自動的爲參數生成鍵)

  • 底層:命名參數的方式往map中用了兩種方式放了鍵值對:第一種方式是以@param的值爲鍵名,第二種方式是以param1、param2···爲鍵名。(注意是一個map中把一個value值放了兩次,只是鍵名不同,兩種方式都能獲取值)

  • 源碼解讀

(@Param(“id”)Integer id, @Param(“lastName”)String lastName);

ParamNameResolver解析參數封裝:

1.names參數值的確定 names: {0=id,1=lastName};構造器的時候就確定好了

​ 確定流程:

​ 1.1 獲取每個標了param註解的參數的@param的值:id,lastName;並賦值給name;

​ 1.2 每次解析一個參數給map中保存信息:(key:參數索引, value:name的值)

​ 如果標註了param註解,則name的值爲註解的值

​ 如果沒有標註:

​ 如果有全局配置:useActualParamName,則name=參數名

​ 否則,name=map.size():相當於當前元素的索引

實例:{0=id, 1=lastName, 2=2}(這裏前兩個參數標明瞭註解,第三個參數沒有標註解 )

public ParamNameResolver(Configuration config, Method method) {
       Class<?>[] paramTypes = method.getParameterTypes();//先獲取所有的參數
       Annotation[][] paramAnnotations = method.getParameterAnnotations();//並且獲取所有參數的註解
       SortedMap<Integer, String> map = new TreeMap();
       int paramCount = paramAnnotations.length;

       for(int paramIndex = 0; paramIndex < paramCount; ++paramIndex) {
           if (!isSpecialParameter(paramTypes[paramIndex])) {
               String name = null;
               Annotation[] var9 = paramAnnotations[paramIndex];
               int var10 = var9.length;

               for(int var11 = 0; var11 < var10; ++var11) {
                   Annotation annotation = var9[var11];
                   if (annotation instanceof Param) {//如果當前參數的註解是param的註解
                       this.hasParamAnnotation = true;
                       name = ((Param)annotation).value();//拿到param註解的value值
                       break;
                   }
               }

               if (name == null) {//如果沒有標註解
                   if (config.isUseActualParamName()) {//如果配置了全局配置
                       name = this.getActualParamName(method, paramIndex);//則使用參數名作爲key值
                   }

                   if (name == null) {//如果沒有標註解
                       name = String.valueOf(map.size());//則name爲map的長度
                   }
               }

               map.put(paramIndex, name); //每次確定一個參數,放入一次
           }
       }

       this.names = Collections.unmodifiableSortedMap(map);
   }

上面的過程將names的值確定好(這個names就是放的註解規定的鍵名),下面將這些鍵名或參數的參數args匹配放入map中返回

public Object getNamedParams(Object[] args) {//傳入的參數:args[1,"Tom"]
       int paramCount = this.names.size();
   	
       if (args != null && paramCount != 0) {
           //如果只有一個元素,並且沒有Param註解,
           if (!this.hasParamAnnotation && paramCount == 1) {
               return args[(Integer)this.names.firstKey()];//即return args[0]。即單個參數直接返回
           } else {//如果是多個元素或者有Param標註
               Map<String, Object> param = new ParamMap();//定義map
               int i = 0;
//給map中保存數據。先遍歷names集合:names = {0=id,1=lastName,2=2}
               for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
                   Entry<Integer, String> entry = (Entry)var5.next();
                   //names集合的value作爲key; names集合的key有作爲取值的參考(即當作數據args的角標)
                   param.put((String)entry.getValue(), args[(Integer)entry.getKey()]); //效果:{id=args[0](即1),lastName=args[1](即Tom),2=args[2]}
                   
                   //額外的將每一個參數保存到map中,使用新的key:param1...paramN
                   String genericParamName = "param" + String.valueOf(i + 1);
                   if (!this.names.containsValue(genericParamName)) {
                       param.put(genericParamName, args[(Integer)entry.getKey()]);
                   }
               }

               return param;
               //所以:有Param註解可以#{指定的key},或者#{param1}
           }
       } else {//否則,參數爲null直接返回
           return null;
       }
   }

2.6 當傳輸參數爲List或Array,Mybatis會將List或Array放在map中

List以list爲鍵,Array以array爲鍵

1.2.3 AdminMapper映射器接口

mapper接口沒有實現類,但是Mybatis會爲這個接口生成一個代理對象

//映射器接口,Mybatis通過反射創建他的對象
public interface AdminMapper {
    Admin queryAdminById(int id);
    int insert(Admin admin);
}

1.2.4 獲取SqlSession,通過getMapper()獲取接口的動態代理實現類,並測試

Mybatis工具第一個核心知識點:SqlSessionFactory

在使用JDBC的時候,使用的都是Connection,我們在Mybatis中使用的是sqlSession。一個sqlSession就是代表和數據庫的一次會話,用完需要關閉。

SqlSession通過SqlSessionFactory.openSession創建來的。sqlSession裏就有了操作數據庫的過程,能直接執行已經映射的sql語句。

/**
 * @author ZAQ
 * @create 2020-04-12 16:47
 */
public class Main {
     //Mybatis是一個針對查詢過程產生映射關係的ORM框架(其他的ORM框架都是對對象產生映射關係)
    public static void main(String[] args) throws IOException {

        String res = "mybatis-config.xml";
        InputStream is = Resources.getResourceAsStream(res);//Mybatis寫的資源加載工具
        //Mybatis工具第一個核心知識點:SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);//只需要在這裏生效一次就行了,一個應用有一個sqlSessionFactory就OK了。
        //在使用JDBC的時候,使用的都是Connection,我們在使用Mybatis的時候,使用的都是SqlSession
        SqlSession session = sqlSessionFactory.openSession();//如果傳入參數true則可以自動提交事務
        //session裏就有了操作數據庫的過程

        //Mapper映射器
       Admin admin = session.selectOne("com.home.java.AdminMapper.queryAdminById",1);//第一個參數:要執行的方法,第二個參數:要傳入的參數
        System.out.println(admin.getAccount());//查詢
       /*另一種寫法
       AdminMapper mapper = session.getMapper(AdminMapper.class);
       Admin admin = mapper.queryAdminById(1);
       System.out.println(admin.getAccount());
        */

       session.commit();//手動提交事務
    }
}	
  • 核心方法getMapper():會通過動態代理模式動態生成UserMapper的代理對象,代理對象去執行增刪改查方法。

  • 兩個核心骨架:SqlSessionFactory、SqlSession(如何查詢數據庫)

    • SqlSession代表和數據庫的一次會話,用完必須關閉
    • SqlSession和Connection一樣,他都是非線程安全的。每次使用都應該獲取新得對象
  • Tomcat中的線程不做交互,一個請求就是一個線程

  • 每個線程都必須有自己的sqlSession。每個請求都佔用一個線程,在所有請求的過程中只創建一個sqlsessionFactory

  • 一個sqlSession對應n個mapper

Mybatis學習內容持續更新中…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章