菜鸟学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}、 p a r a m 2 , 但 是 要 注 意 {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学习内容持续更新中…

猜你喜欢

转载自blog.csdn.net/ysf15609260848/article/details/105959861