一文读懂java框架之Mybatis一

放在前边以表重要
1)mybatis的接口绑定是不支持方法重载的.
2)其中的映射配置文件中的SQL语句不需要加";".
3)"& lt;"小于 "& gt; "大于

  1. 什么是框架?
    简言之,就是一群程序员为了减少代码冗余,提高自身开发速度,封装好的一些代码,这些框架代码没有业务逻辑,使用简单,并且可以简化代码,其他程序员可以添加自己的业务逻辑来达到快速开发和迭代的效果,框架中大量的使用了反射以及各种设计模式(反射会让java性能下降,这个我们之后再聊),使得使用框架很简单,但是掌握框架的运行原理比较复杂。该框架不需要服务器。

  2. 如何学习框架?
    当我们遇到一个新的框架时,什么是合理的学习路线?
    1)找到框架提供的资源(*.jar)
    2)项目中导入jar包
    3)提供相应的配置文件(xml,properties,yml…)
    4)学习框架给的API

  3. 创建MyBatis环境
    1)添加依赖
    在这里插入图片描述
    依次是解析xml的jar,单元测试的jar,mybatis核心jar,数据库连接jar。
    2)编写Mybatis核心配置文件
    格式要求是xml文件,命名和存放位置没有要求,但是为了方便加载一般放到src路径下默认在ClassPath路径下加载配置文件,后续需要手动去加载配置文件。

<?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资源文件-->
    <properties resource="db.properties"/>
    <!--配置系统设置-->
    <settings>
        <!--开启自动驼峰命名-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    <!--配置Mybatis的核心运行环境default用于指定当前生效的是哪个环境-->
    <environments default="dev">
        <!--用于指定一个环境,可以出现多个,id用于唯一标识当前环境-->
        <environment id="dev">
            <!--事务管理器,JDBC表示采用和JDBC一致的方式来管理事务还可以选择MANAGED,表示交给其他框架管理事务-->
            <transactionManager type="JDBC"></transactionManager>
            <!--数据源,POOLED表示采用数据库连接池获取数据库连接,UNPOOLED表示自己手动创建数据连接,JNDI 表示交给其他框架创建链接-->
            <dataSource type="POOLED">
                <!--软编译必须用${}括起来表示否则会报找不到类的错误-->
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--扫描mapper文件-->
    <mappers>
        <!--resource表示用于加载当前项目下的映射文件,url表示加载远程的资源文件,class表示加载类-->
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

需要特殊说明是数据源中JNDI(Java Naming and Directory Interface,Java命名和目录接口)
3)准备要操作的数据库表格和数据
4)创建实体类

public class User implements Serializable {
    private Integer id;
    private String name;
    private String gender;
    private Integer age;
    private Date bir;
    private Integer score;
    private Date reg;

5)编写映射文件即java→数据库
映射文件主要是用于定义要执行的SQL语句,同时声明数据库表格和对象之间的映射关系,命名和位置也没有要求,为了方便加载一般也放到src目录下。

<?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属性表示命名空间,类似于java中的package用来定位sql,必须写不写会报错-->
<mapper namespace="abc">
    <!--select标签用于指定查询的SQL语句
    id属性用于唯一标识sql语句,类似于类中的方法名
    resultType属性用于告诉MyBatis要将查询到结果封装成什么对象
    -->
    <select id="selAll" resultType="com.sxt.pojo.User">
        select * from student
    </select>
    <select id="selOne" resultType="java.lang.Integer">
        select count(*) from student;
    </select>
    <select id="selMap" resultType="com.sxt.pojo.User">
        select * from student;
    </select>
</mapper>

这里需要注意的是resultType这里不明白为什么要返回对象类型,可以看我们之前自己封装的DML和DQL小框架,从中可以知道我们是通过反射Class cls得到结果类型的来达到复用的目的,因此我们需要将返回的结果类型告诉Mybatis,使Mybatis来帮助我们处理结果。

/**
     * 统一执行DML的方法
     * @param sql
     * @param params
     * @return
     */
    public int update(String sql,Object... params){
        Connection con = JDBCUtils.getCon();
        PreparedStatement pstmt = JDBCUtils.getPstmt(con, sql);
        JDBCUtils.bindParams(pstmt,params);
        try {
            return pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.close(null,pstmt,con);
        }
        return 0;
    }

    /**
     * 通用的DQL方法
     * @param cls
     * @param sql
     * @param params
     * @param <E>
     * @return
     */
    public <E> List query(Class<E> cls, String sql, Object... params){
        List<E> list = new ArrayList<>();
        ResultSet rs=null;
        Connection con = JDBCUtils.getCon();
        PreparedStatement pstmt = JDBCUtils.getPstmt(con, sql);
        JDBCUtils.bindParams(pstmt,params);
        try {
            rs = pstmt.executeQuery();
            while(rs.next()){
                //通过反射创建需要的对象
                E e = cls.newInstance();
                //获得当前对象下所有的属性
                Field[] fields = cls.getDeclaredFields();
                for (Field f:fields) {
                    //获取当前属性的名称
                    String name = f.getName();
                    //拼接该属性对应的set方法名
                    String setMethodName = "set" + name.substring(0, 1).toUpperCase()+ name.substring(1);
                    //通过反射获取set方法
                    Method setMenthod = cls.getMethod(setMethodName, f.getType());
                    //调用setMethod堆属性进行赋值
                    setMenthod.invoke(e,rs.getObject(name));
                }
                //加入list
                list.add(e);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

单元测试类

public class TestMybatis {
    @Test
    public void testMybatis() throws IOException {
        //加载Mybatis核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis.xml");
        //先构建SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        //打开SqlSession对象
        SqlSession sqlSession = factory.openSession();
        //通过SqlSession执行查询操作 ,Mybatis通过namespace+id的形式定位SQL
        List<User> users = sqlSession.selectList("abc.selAll");
        //处理结果
        for (User user : users) {
            System.out.println(user);
        }
        int count = sqlSession.selectOne("abc.selOne");
        System.out.println(count);
        Map<Integer, User> map = sqlSession.selectMap("abc.selMap","id");
        System.out.println(map);
        //关闭资源
        sqlSession.close();
    }
}

我们看到有三种处理结果方法分别是selectList、selectOne、selectMap,但是底层实现都是selectList由于篇幅问题,此处不再展示。
其中当我们的映射文件中sql语句的id全局唯一时,namespace可以省略。在这里插入图片描述
测试结果:在这里插入图片描述
4、Mybatis实现条件查询
三个常用的查询方法:selectlist,selectOne,selectMap,都允许在调用的时候传递参数,参数类型是Object,个数只能传递一个,如果传递多个参数,需要处理成单个对象。同时也需要相应的修改,要使用占位符替代接收的参数,占位符有两种,分别是#{},$ {},#{}从底层使用的是JDBC的PreparedStatement,$ {}底层使用的是Statement。
1、#{}占位符
#{}作为SQL语句的占位符,MyBatis允许接受三种类型的参数
1.1简单类型的参数
简单类型指的是基本数据类型,包装类型,String,java.sql,java.sql.*…,此时,#{}会忽略占位符的名称和个数(即{}中写什么都行),将参数进行绑定,parameterType可以指定参数的类型,如果省略,表示参数类型为Object,一旦指定,传参是必须类型一致,否则会报类型转换异常。
所以一般开发中默认省略,只有像用来限制参数类型的需求中会用到parameterType标签.
我们通过log4j日志框架同样可以看出来,这里#{}的底层使用的是JDBC中的PreparedStatement发射器,首先将SQL语句进行了预编译.在这里插入图片描述

    <select id="selById" resultType="com.bupt.pojo.User" parameterType="java.lang.Integer">
        select * from student where id=#{suibian}
        </select>
@Test
    public void selBind() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.selById(1003);
        sqlSession.close();

    }
}

1.2Map类型的参数
当有多个参数传递的时候,将多个参数封装到Map集合,#{}需要使用Map中对应的key去取值(注意:此时我们的{}中只能写key,不能像简单参数中随便写了),如果key不存在,不会报错,拿到的是null.
注意:此时我并没有写参数类型,原因同上

    <select id="sel4Log4j" resultType="com.bupt.pojo.User">
        select * from student where name=#{name} and psw=#{pwd}
    </select>
    @Test
    public void testSel4LoginByMap(){
        SqlSession session = MybatisUtils.getSqlSession();
        Map<String, Object> params = new HashMap<>();
        params.put("name","小昭");
        params.put("pwd","123");
        session.selectOne("sel4Log4j",params);
        session.close();
    }

1.3POJO类型的参数
多个参数封装为对应的POJO类,此时,#{}需要调用该POJO类中对应的getter方法取值,其实底层就是我们在上文中封装DQL操作通过反射来得到get方法进行取值如果没有对应的getter方法,则抛出异常.

    @Test
    public void testSel4LoginByPojo() {
        SqlSession session = MybatisUtils.getSqlSession();

        User user = new User();
        user.setName("小昭");
        user.setPwd("123");

        session.selectOne("sel4Log4j", user);

        session.close();
    }

在这里插入图片描述
那么我们说了那么多,什么时候用${}呢?
当SQL语句结构不确定时,不能使用#{}占位符,原因是#{}底层使用的是PareparedStatement,会对SQL语句进行预编译(这里会遇到mybatis进化成那个遇到的一个报错信息,由于篇幅问题,见这篇文章),导致SQL语句无法被补全.此时可以用 ${}来进行操作.
其底层由statement实现,这里就是之前说的SQL注入出问题的发射器,我们可以看到就是一个拼接字符串的过程在这里插入图片描述

    @Test
    public void sel(){
        SqlSession session = MybatisUtils.getSqlSession();
        Map<String, Object> params = new HashMap<>();
        params.put("tName","student");
        params.put("age","age");
        params.put("cvalue",20);
        params.put("order","order by id desc");
        session.selectList("sel",params);
        session.close();
    }

4 增删改操作
增删改操作涉及到了事务管理,Mybatis中,默认将JDBC的自动管理事务机制关闭了,要求所有的增删改操作都必须进行手动提交,同样通过SqlSession来进行事务管理.
4.1新增数据
此处注意,insert语句的顺序要与表中的字段顺序一致
在这里插入图片描述

@Test
    public void ins(){
        SqlSession session = MybatisUtils.getSqlSession();
        User user = new User();
        user.setName("高斯里");
        user.setAge(58);
        user.setBir(new Date());
        user.setGender("男");
        user.setPwd("123");
        user.setScore(80);
        try {
            session.insert("ins", user);
            session.commit();
        } catch (Exception e) {
            session.rollback();
            e.printStackTrace();
        }
    }

小结:useGeneratedKeys="true"表示要获取自动增长的主键,keyProperty="id"表示将获取到的主键值赋值给id属性,会自动调用setId方法

这里我们要注意其实增删改的底层操作就是封装的JDBC中statement或者它的子类PreparedStatement发射其中的executeUpdate方法,故insert,update,delete的方法和标签都是一样的.
4.2类型别名
主要针对映射文件中定位类型时使用,简化配置,分为两种情况.在这里插入图片描述
4.1.2内建别名
别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator
4.1.3自定义别名
在这里插入图片描述
< typeAlias >表示给当前类设置别名,并且别名为user,但是alias字段可以省略,省略之后代表的是当前类,别名为当前类名,不区分大小写.
< package >当我们的实体类数量太多,一个一个写< typeAlias>也很麻烦,就会用到< package>表示当前包下的所有实体类都支持类名表示别名,并且不区分大小写.
5 接口绑定

  1. 为什么需要接口绑定呢?

2点:1)提供的方法不灵活,2)参数传递复杂
因为通过前边的CURD,我们发现方法调用不灵活,查询只有selectList(),selectOne,selectMap(),底层还都是selectList实现的,delete,update,insert还都是insert,那我是根据ID查呢?这是不灵活.还有就是参数的传递过于复杂

  1. 接口绑定解决的问题
    MyBatis中的接口绑定方案, 指的是将一个用户自定义的接口和一个对应的映射配置文件进行绑定. 程序员只需要提供接口及抽象方法, 同时将对应要执行的SQL语句写到映射文件中, 然后, MyBatis会将接口和映射文件进行绑定, 相当于接口的实现类交给MyBatis完成了. 我们只需要写接口和映射文件中的sql就行,不用写实现类
    要想实现接口绑定, 程序员必须遵循一定的规范
  2. 需要遵循的规范

3.1 必要规范
1)namespace的值必须和对应接口的全限定路径一致;
2)SQL语句标签的id属性必须和当前绑定的接口中对用方法的名称一致;
3.2 可选规范
1)接口的名称和映射文件的名称一致;
2)将接口和映射文件放在相同的位置;.
我们知道必要规范不遵循无法使用接口绑定,
那么我们遵循可选规范有什么用呢?

通过package统一扫描映射
在这里插入图片描述
当我们的映射配置文件越来越多,每次都需要需改mybatis核心的配置文件也是很麻烦的,因此当我们满足可选规范的时候,我们可以通过下面来扫描整个包在这里插入图片描述

  1. 参数传递

接口绑定中, 支持多种参数传递方式:
1)简单类型, 2)Map类型 3)Pojo类型跟之前一样;
4)多参数传参:
1 不使用注解, 可以通过arg(0开始)或param(1开始)形式获取;
2 使用@Param(key)注解时, 可以通过key或param形式获取.在这里插入图片描述

    @Test
    public void selBind() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //查询所有,无参
        mapper.selAll();
        //根据id查询,简单参数传递
        mapper.selById(1003);
        //条件查询,map传参查询
        Map<String, Object> map = new HashMap<>();
        map.put("uname","小昭");
        map.put("age",20);
        mapper.selByMap(map);
        //条件查询,对象传参查询
        User user = new User();
        user.setName("小昭");
        mapper.selByPojo(user);
        //多条件,多参数查询
        mapper.sel4Log4j("小昭","123");
        sqlSession.close();
    }
public interface UserMapper {
    List<User> selAll();
    User selById(int id);
    List<User> selByMap(Map<String, Object> params);
    List<User> selByPojo(User user);
    User sel4Log4j(@Param("name") String name, @Param("pwd") String pwd);
}
  1. 动态SQL
    MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态SQL这一特性可以彻底摆脱这种痛苦。
    6.1< if > :用于条件判断, test属性表示判断结果, 要求是一个boolean值.
    6.2 < where>:
    用于维护where子句, 通常配合一起使用. 如下功能:
    当没有条件时, 不会创建WHERE关键字;
    当有条件时, 会自动生成WHERE关键字;
    会自动去掉第一个条件的and/or关键字.
    6.3.< choose>< when>< otherwise>
    功能类似于java中的switch…case…default. 多分支判断, 只能成立一个条件.
    6.44.< bind>
    对数据进行加工, 通常用于处理模糊查询的数据.在这里插入图片描述
    在这里插入图片描述
    6.5< sql>< include>
    提取SQL片段和引用SQL片段
    在这里插入图片描述
    6.6.< set>
    用于维护更新语句中的set子句, 类似于的功能.
    当条件不成立时, 不会创建SET关键字;
    当条件成立时, 会自动生成SET关键字;
    会自动去掉最后一个条件的逗号(,).
    在这里插入图片描述
    6.7.< foreach>
    对数组, List, Set, Map集合进行遍历时使用, 通常用于in操作中. 一般用于实现批量新增或批量删除操作. 属性介绍:
    collection: 要遍历的集合
    item: 迭代项
    open: 以什么字符开头
    close: 以什么字符结束
    separator: 多个迭代项之间的分隔符
    在这里插入图片描述
    6.8< trim>
    用于对数据进行处理, 可以在指定的字符串前后进行操作.
    where和set自动增加和去除and和","用的就是trim(),
    prefix前边加,suffix后边加,suffixoverrides后边省略在这里插入图片描述
    . 7.注解开发

1.什么是注解
注解是用在代码中的, 通常用于简化配置文件. 注解使用时要求需要先导包. 注解的语法:
@注解名(属性=值,…)
2.已经使用过的注解
@Override: 用在方法上, 约束方法的重写;
@Test: 用在方法上, 用于单元测试;
@Param: 用在方法的参数上, 用于将多个参数封装为Map集合, 需要给参数命名.
3.元注解
jdk中提供的最初的注解, 用于定义注解, 描述注解的注解. 一共有四个:
@Target: 表示注解的使用位置
@ Retention: 表示注解在何时生效, SOURCE(源码), CLASS(字节码), RUNTIME(运行时)
@Documented: 是否运行出现在javadoc中
@Inherited: 是否允许被子类继承
4.关于注解的属性
大部分注解中有一个默认属性, 默认属性叫value;
如果属性的值是简单类型(基本类型, String, Resource, Class, …), 属性名=属性值;
如果属性的值是数组类型, 属性名={值1, 值2, …};
如果数组的元素只有一个, 可以省略大括号;
如果属性只有一个, 而且是value属性, 那么属性名也可以省略;
如果属性是对象, 属性名=@类型(属性=值, …)
5.MyBatis中常用的注解
使用MyBatis中的注解, 可以简化映射文件的配置. 注意, 动态SQL不能简化.
@Select, 用于简化查询配置
@Insert, 用于简化新增配置
@Update, 用于简化更新配置
@Delete, 用于简化删除配置

发布了219 篇原创文章 · 获赞 352 · 访问量 21万+

猜你喜欢

转载自blog.csdn.net/qq_42859864/article/details/103689961
今日推荐