简介

  • 一个orm框架,与Spring Data Jpa分庭抗礼。
  • 项目整合Mybatis框架的话大部分公司使用mappers层代替dao层,定义一些增删查改的接口。

    代码实例

  • 举个栗子,实现学生的增操作
  • 先贴jar包:mybatis的jar包、MySQL的驱动包、log4j的jar包
  • 写model包实体类;

    public class Student {
        private Integer id;
        private String name;
        private Integer age;
    
        //getter、setter
    }
    
  • 配置mybatis-config.xml文件引入jdbc资源文件(待写)以配置数据源、给实体类起别名、配置映射器引入数据层接口的映射文件(待写)等;

    <?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 resource="jdbc.properties"/>
        <!-- 直接配置 -->
        <!-- 
        <properties>
            <property name="jdbc.driverClassName" value="com.mysql.jdbc.Driver"></property>
            ...
        </properties>
         -->
        <!-- 给类起别名 -->
        <typeAliases>
            <typeAlias alias="Student" type="com.java1234.model.Student"/>
        </typeAliases>
        <!-- 推荐,扫描包,全部起类名为别名,不能自定义 -->
        <!-- 
        <typeAliases>
            <package name="com.java1234.model"/>
        </typeAliases>
         -->
        <!-- 创建环境 -->
        <environments default="development"> <!-- 默认环境定义为开发环境,与下面的id对应 -->
            <!-- 创建一个名为development的环境 -->
            <environment id="development">
                <!-- 由jdbc实现事务管理 -->
                <transactionManager type="JDBC" />
                <!-- 数据源为连接池 -->
                <dataSource type="POOLED">
                    <property name="driver" value="${jdbc.driverClassName}" /> <!-- value取的是jdbc配置文件里的属性 -->
                    <property name="url" value="${jdbc.url}" />
                    <property name="username" value="${jdbc.username}" />
                    <property name="password" value="${jdbc.password}" />
                </dataSource>
            </environment>
            <!-- 一般还会创建生产、测试等环境 -->
        </environments>
        <!-- 映射器 -->
        <mappers>
            <!-- 直接找到实体类的映射文件 -->
            <mapper resource="com/java1234/mappers/StudentMapper.xml" />
            <!-- 通过实体类找其配置文件,不要求配置文件也不用和实体类命名相同 -->
            <mapper class="com.java1234.mappers.StudentMapper"/>
            <!-- 扫描包下所有配置文件,推荐 -->
            <package name="com.java1234.mappers"/>
        </mappers>
    </configuration>
    
    //jdbc.properties
    jdbc.driverClassName=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/db_mybatis
    jdbc.username=root
    jdbc.password=123456
    
    • dataSource
      • UNPOOLED:没有连接池,每次数据库操作,mybatis都会创建一个新的连接,用完后关闭;适合小并发项目
      • POOLED:常用,可以定连接个数,每次都从连接池里拿一个连接,用完后放回去
      • JNDI:使用应用服务器配置JNDI数据源获取数据库连接
  • util包写session工厂类

    /**
     * session工厂
     * @author 14103
     */
    public class SqlSessionFactoryUtil {
    
        //静态域,类的所有实例共享一个sqlSessionFactory,一个对象对其修改则所有对象的该域都被修改
        private static SqlSessionFactory sqlSessionFactory;
    
        //单例模式:获取session工厂
        private static SqlSessionFactory getSqlSessionFactory() {
            if(sqlSessionFactory==null) {
                InputStream inputStream=null;
                try {
                    //把mybatis的配置文件搞成输入流
                    inputStream=Resources.getResourceAsStream("mybatis-config.xml");
                    //用这个输入流来创建session工厂
                    sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
                }catch(Exception e) {
                    e.printStackTrace();
                }
            }
            return sqlSessionFactory;
        }
    
        //打开一个session,相当于打开一个数据库连接
        public static SqlSession openSession() {
            return getSqlSessionFactory().openSession(); //用返回的session工厂打开
        }
    }
    
  • mappers包写数据层接口(定义增删查改等接口方法)

    /**
     * 操作数据库的接口
     * @author 14103
     */
    public interface StudentMapper {
    
        //返回影响的记录数,默认会返回影响行数的,不用在配置文件定义返回类型
        public int add(Student student);
    
        public int update(Student student);
    
        public int delete(Integer id);
    
        public Student findById(Integer id);
    
        public List<Student> find();
    }
    
  • StudentMapper.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">
    <!-- 为指定接口中的方法配置sql语句 -->
    <mapper namespace="com.java1234.mappers.StudentMapper"> <!-- 对应接口的完整路径 -->
        <!-- 定义一个集合,名为StudentResult -->
        <resultMap type="Student" id="StudentResult">
            <!-- 定义主键 -->
            <id property="id" column="id"/> <!-- 类属性与表字段对应 -->
            <!-- 定义普通字段 -->
            <result property="name" column="name"/>
            <result property="age" column="age"/>
        </resultMap>
    
        <select id="findStudentWithAddress" resultMap="StudentResult" parameterType="Integer">
            select * from t_student t1,t_address t2 where t1.id=#{id} and t2.id=t1.addressId 
            <!-- t2.id=t1.addressId,等号左右调换也一样;去掉t_address的查询也可以;
            中括号内是属性而非字段名-->
        </select>
    
        <insert id="add" parameterType="Student"> <!-- id是方法名,后面是入参类型(用的是mybatis配置文件的别名 -->
            insert into t_student values(#{id},#{name},#{age}) 
            <!-- sql,大括号内是字段;id想要数据库自增的话可以只设置着占位(或者写个null)
            ,不传值,即实体类可以不setId,但是底层还是会调用占位的所有字段的getter,
            所以实体类必须有它们的getter;不想占位可以写成:
            insert into t_student(name,age) values(#{name},#{age}) -->
        </insert>
    
        <update id="update" parameterType="Student">
            update t_student set name=#{name},age=#{age} where id=#{id}
        </update>
    
        <delete id="delete" parameterType="Integer">
            delete from t_student where id=#{id}
        </delete>
    
        <select id="findById" parameterType="Integer" resultType="Student">
            select * from t_student where id=#{id}
        </select>
    
        <select id="find" resultMap="StudentResult"> <!-- 返回类型为集合 -->
            select * from t_student
        </select>
    </mapper>
    
  • 如果parameterType(入参类型)是String的话,那么不能用#{xxx}(抛异常:there is no getter for property xxx),而要用#{_parameter}.

    <select id="fuzzySearch" resultMap="GoodsResult"> //fuzzySearch只有一个参数且为string类
        select * from goods where number=#{_parameter} or name like '%${_parameter}%' or type=#{_parameter} order by Report_Date_Time desc
    </select>
    
  • 配置log4j日志

    <!--根节点,指定日志等级及输出目标-->
    log4j.rootLogger=info,appender1,appender2
    
    log4j.appender.appender1=org.apache.log4j.ConsoleAppender 
    
    log4j.appender.appender2=org.apache.log4j.FileAppender 
    log4j.appender.appender2.File=C:/logFile.txt
    
     <!--指定日志输出的布局模式-->
    log4j.appender.appender1.layout=org.apache.log4j.TTCCLayout
    log4j.appender.appender2.layout=org.apache.log4j.TTCCLayout
    
  • 写测试类

    /**
     * 用main做个测试
     * @author 14103
     */
    public class StudentTest {
    
        private static Logger logger=Logger.getLogger(StudentTest.class);
    
        public static void main(String[] args) {
            //打开一个数据库连接
            SqlSession sqlSession = SqlSessionFactoryUtil.openSession();
            //传入一个含有数据库操作的接口,mybatis会把它和配置了sql的xml文件捆绑起来
            StudentMapper studentMapper=sqlSession.getMapper(StudentMapper.class);
            Student student=new Student(3,"李四",11);
            //然后这个接口的方法就能用了
            int result=studentMapper.add(student); //直接传入对象,mybatis自动获取属性传入占位符
            //提交事务
            sqlSession.commit();
            if(result>0) {
                logger.info("添加成功!");
            }
        }
    }
    

    一对一表关联

  • 学生与地址之间的一对一关联
  • 分别创建学生类、地址类
  • 写学生类的数据层接口

    public interface AddressMapper {
    
        public Address findById(Integer id);
    
    }
    
  • 配置映射文件

    <mapper namespace="com.java1234.mappers.StudentMapper"> <!-- 对应接口的完整路径 -->
        <!-- 定义一个返回结果集合,名为StudentResult -->
        <resultMap type="Student" id="StudentResult">
            <!-- 定义主键 -->
            <id property="id" column="id"/> <!-- 类属性与表字段对应 -->
            <!-- 定义普通字段 -->
            <result property="name" column="name"/>
            <result property="age" column="age"/>
    
            <!-- 表关联,推荐;后面三种不推荐 -->
            <association property="address" column="addressId" 
             select="com.java1234.mappers.AddressMapper.findById"> <!-- 查到目标记录发现addressId字段时,会传入这个select方法中,从而查找到关联的地址并赋给address属性-->
            </association>
            <association property="grade" column="gradeId" 
             select="com.java1234.mappers.GradeMapper.findById">
            </association>
    
            <!-- 
            //对象级联
            <result property="address.id" column="addressId"/> //要用主表的外键字段
            <result property="address.sheng" column="sheng"/> //要用关联表的字段
            <result property="address.shi" column="shi"/> 
            <result property="address.qu" column="qu"/> 
             -->
    
            <!--  
            //嵌套
            <association property="address" javaType="Address">
                <result property="id" column="id"/>
                <result property="sheng" column="sheng"/>
                <result property="shi" column="shi"/>
                <result property="qu" column="qu"/>
            </association>
             -->
    
             <!-- 
            //直接关联,需要另外为Address定义一个结果集合(虽然是一对一,但是集合里面只有一个地址也没事的
            <association property="address" resultMap="AddressResult"/>
             -->
        </resultMap>
    
        <!-- 
        <resultMap type="Address" id="AddressResult">
            <result property="id" column="id"/>
            <result property="sheng" column="sheng"/>
            <result property="shi" column="shi"/>
            <result property="qu" column="qu"/>
        </resultMap>
         -->
    
        <select id="findStudentWithAddress" resultMap="StudentResult" parameterType="Integer">
            select * from t_student t1,t_address t2 where t1.addressId=t2.id and t1.id=#{id}
        </select>
    </mapper>
    
  • 在学生类的数据层接口中写个查询方法

    /**
     * 操作数据库的接口
     * @author 14103
     */
    public interface StudentMapper {
        public Student findStudentWithAddress(Integer id);
    }
    
  • 在学生类的数据层接口映射文件中绑定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">
    <!-- 为指定接口中的方法配置sql语句 -->
    <mapper namespace="com.java1234.mappers.StudentMapper"> <!-- 对应接口的完整路径 -->
        <!-- 定义一个返回结果集合,名为StudentResult -->
        <resultMap type="Student" id="StudentResult">
            <!-- 定义主键 -->
            <id property="id" column="id"/> <!-- 类属性与表字段对应 -->
            <!-- 定义普通字段 -->
            <result property="name" column="name"/>
            <result property="age" column="age"/>
    
            <!-- 表关联,需要另外实现Address的数据层接口和映射文件,推荐;后面三种不推荐 -->
            <association property="address" column="id" 
             select="com.java1234.mappers.AddressMapper.findById"> <!-- column指明子表中与父表关联的字段,查到目标记录发现addressId字段时,会作为下面select方法绑定的t_address表的指定column——id传入方法中,从而查找到关联的地址 -->
            </association>
    
            <!-- 
            //对象级联
            <result property="address.id" column="addressId"/> //要用主表的外键字段
            <result property="address.sheng" column="sheng"/> //要用关联表的字段
            <result property="address.shi" column="shi"/> 
            <result property="address.qu" column="qu"/> 
             -->
    
            <!--  
            //嵌套
            <association property="address" javaType="Address">
                <result property="id" column="id"/>
                <result property="sheng" column="sheng"/>
                <result property="shi" column="shi"/>
                <result property="qu" column="qu"/>
            </association>
             -->
    
             <!-- 
            //直接关联,需要另外为Address定义一个结果集合(虽然是一对一,但是集合里面只有一个地址也没事的
            <association property="address" resultMap="AddressResult"/>
             -->
        </resultMap>
    
        <!-- 
        <resultMap type="Address" id="AddressResult">
            <result property="id" column="id"/>
            <result property="sheng" column="sheng"/>
            <result property="shi" column="shi"/>
            <result property="qu" column="qu"/>
        </resultMap>
         -->
    
        <select id="findStudentWithAddress" resultMap="StudentResult" parameterType="Integer">
            select * from t_student t1,t_address t2 where t1.addressId=t2.id and t1.id=#{id}
        </select>
    </mappers>
    
  • 一对多映射
  • 写个年级类,年级与学生一对多(数据库的话要不要定义成外键都没关系)

    public class Grade {
    
        private Integer id;
        private String gradeName;
        private List<Student> students;
    
        //getter、setter
    
        @Override
        public String toString() {
            return "Grade [id=" + id + ", gradeName=" + gradeName + ", students=" + students + "]";
        }
    }
    
  • 给年级类写个数据层接口

    public interface GradeMapper {
    
        public Grade findById(Integer gradeId);
    
    }
    
  • 配置年级类映射文件

    <mapper namespace="com.java1234.mappers.GradeMapper"> <!-- 对应接口的完整路径 -->
    
        <resultMap type="Grade" id="GradeResult">
            <result property="id" column="id"/>
            <result property="gradeName" column="gradeName"/>
            <collection property="students" column="id" 
             select="com.java1234.mappers.StudentMapper.findByGradeId"> <!-- 调用下面findById查到年级表的id时传入这个学生表的方法中,查到学生并赋给students属性 -->
            </collection>
        </resultMap>
    
        <select id="findById" parameterType="Integer" resultMap="GradeResult">
            select * from t_grade where id=#{id}
        </select>
    
    </mapper>
    
  • 给学生类数据层接口加个findByGradeId方法,然后到映射文件中实现该方法

    <select id="findByGradeId" resultMap="StudentResult" parameterType="Integer">
        select * from t_student where gradeId=#{gradeId}
    </select>
    
  • 写个测试

    @Test
    public void testFindGradeWithStudents() {
        logger.info("查询年级(带学生)");
        Grade grade=gradeMapper.findById(1);
        System.out.println(grade);
    }
    
  • 目前我们还不能通过学生来获取年级信息,下面我们在学生端也添加年级的获取方式,实现双向关联
  • 学生类加个grade属性,toString加个年级

    /*这里要是加个打印grade属性的话,会调用grade类的tostring方法,
    * 而其中又包含打印students属性,如此往复循环将会报错:StackOverflowError
    */
    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + ", age=" + age
                + ", address=" + address + ", grade=" + grade.getGradeName() +"]"; 
    }
    
  • 给学生类映射文件中定义的返回类型StudentResult加个grade属性及其获取方式,这样只要返回类型是StudentResult的查询方法都会获取到grade信息

    <association property="grade" column="gradeId" 
     select="com.java1234.mappers.GradeMapper.findById"> <!-- 就算设置了外键也必须写这个方法,否则mybatis是不会自动去查t_grade表的 -->
    </association>
    
  • 写个测试

    @Test
    public void testFindStudentWithGrade() {
        logger.info("查询学生及其地址、年级");
        /*借用findStudentWithAddress就能查到年级了,
         * 但是用findById是查不到的,
         * 因为映射文件中它的返回类型指定为Student而非StudentResult
         */
        Student student=studentMapper.findStudentWithAddress(2); 
        System.out.println(student);
    }
    
  • 代码实例:MybatisHelloWorld

    动态sql

  • if标签:检测不为空的字段进行条件拼接
  • choose标签:指定搜索字段,指定了之后就算还有其他字段传进来也不起作用
  • where标签:拼接条件时自动把第一个子句的and|or去掉
  • trim:提供前后缀、前后缀覆盖方案
  • foreach:遍历条件集合
  • set:自动把参数放进update的坑位里

    <select id="searchStudent" parameterType="Map" resultMap="StudentResult"> <!-- 也可以是HashMap之类;传入自定义的类,属性的获取方式也跟下面一样,直接写属性名,而不用调用getter --> 
        select * from t_student where gradeId=#{gradeId}
        <if test="name!=null">and name like #{name}</if> <!-- like的话等下传进来的name两边要加个% -->
        <if test="age!=null">and age=#{age}</if>
    </select>
    
    <!-- 淘宝搜索有个下拉框可以指定按照宝贝或店铺来查询 -->
    <select id="searchStudent2" parameterType="Map" resultMap="StudentResult"> <!-- 也可以是HashMap之类 --> 
        select * from t_student
        <choose>
            <when test="searchBy=='gradeId'">where gradeId=#{gradeId}</when>
            <when test="searchBy=='name'">where name like #{name}</when>
            <otherwise>where age=#{age}</otherwise>
        </choose>
    </select>
    
    <select id="searchStudent3" parameterType="Map" resultMap="StudentResult"> <!-- 也可以是HashMap之类 --> 
        select * from t_student
        <where> <!-- 拼接的时候自动把第一个子句的and去掉 -->
            <if test="gradeId!=null">gradeId=#{gradeId}</if> <!-- 加不加and随意 -->
            <if test="name!=null">and name like #{name}</if> <!-- 从第二个if开始就要加and了 -->
            <if test="age!=null">and age=#{age}</if>
        </where>
    </select>
    
    <select id="searchStudent4" parameterType="Map" resultMap="StudentResult"> <!-- 也可以是HashMap之类 --> 
        select * from t_student
        <trim prefix="where" prefixOverrides="and|or"> <!-- 覆盖掉子句的前缀and|or;这样就跟where标签一样了 -->
            <if test="gradeId!=null">gradeId=#{gradeId}</if>
            <if test="name!=null">and name like #{name}或者and name like '%${suthor}%'</if>
            <if test="age!=null">and age=#{age}</if>
        </trim>
    </select>
    
    <!-- 在某段年级范围内查找学生 -->
    <select id="searchStudent5" parameterType="Map" resultMap="StudentResult"> <!-- 也可以是HashMap之类 --> 
        select * from t_student
        <if test="gradeIds!=null">
            <where>
                gradeId in
                <!-- 把所有元素搞成(?,?,?) -->
                <foreach collection="gradeIds" item="gradeId" open="(" separator="," close=")">
                    #{gradeId}
                </foreach>
            </where>
        </if>
    </select>
    
    <update id="updateStudent" parameterType="Student">
        update t_student
        <set> <!-- set能自动把最后一个逗号去掉 -->
            <if test="name!=null">name=#{name},</if> <!-- 记得加逗号 -->
            <if test="age!=null">age=#{age},</if>
        </set>    
        where id=#{id}
    </update>
    
  • 字符串型时间的比较、不同类型的判空操作、特殊符号的写法

    <!--查询在一下时间段内的记录-->
    <if test="reportDateTime1!=null">and Report_Date_Time>=#{reportDateTime1}</if> <!--字符型的时间可以直接这样比-->
    <if test="reportDateTime2!=null">and Report_Date_Time <![CDATA[ <= ]]> #{reportDateTime2}</if>
    <if test="number!=null">and number=#{number}</if> <!--包装在类中,则number!=0(Goods把number定义为long型,默认值为0,;包装在Map中,则number!=null(Map把它定义为Object,默认值为null)-->
    
  • 代码实例:MybatisActiveSql

    处理clog、blob

  • 数据库字段对应为longtext、longblob(准确来说是blog,但是它有点小),类属性对应为byte[]、String
  • 给Student类加俩属性

    public class Student {
        private Integer id;
        private String name;
        private Integer age;
        //blob对应为字节数组
        private byte[] pic;
        //clog则是String
        private String remark;
    }
    
  • 数据层接口写个保存和获取数据的方法

    <insert id="insert" parameterType="Student">
        insert into t_student values(#{id},#{name},#{age},null,null,#{pic},#{remark})
    </insert>
    
    <select id="getStudentById" parameterType="Integer" resultType="Student">
         select * from t_student where id=#{id}
    </select>
    
  • 测试方法

    @Test
    public void testInsert() throws IOException {
        logger.info("添加学生");
        Student student=new Student(4,"张三4",14);
        student.setRemark("很长的文本…");
        //插入图片
        File file=new File("d://puchijun.jpg");
        InputStream bis=new BufferedInputStream(new FileInputStream(file));
        //创建数组
        byte[] pic=new byte[bis.available()]; //available()是bis的字节长度
        //把图片读取到数组
        bis.read(pic);
        bis.close();
        student.setPic(pic); //最终放进去的是字节数组
        studentMapper.insert(student);
        sqlSession.commit();
    }
    
    @Test
    public void testGetStudentById() throws IOException {
        logger.info("通过id查找学生");
        Student student=studentMapper.getStudentById(4);
        System.out.println(student);
        byte[] pic=student.getPic(); //获取到的原始类型是字节数组
        File file=new File("d://puchijun2.jpg");
        OutputStream outputStream=new BufferedOutputStream(new FileOutputStream(file));
        outputStream.write(pic);
        outputStream.close();
    }
    

    Mybatis杂项

    分页
  • 逻辑分页:把所有记录都查询出来,然后取出指定数目,并不是真正的分页
  • 数据层接口

    /**
     * 操作数据库的接口
     * @author 14103
     */
    public interface StudentMapper {
        //逻辑分页
        public List<Student> findStudent(RowBounds rowBounds);
    }
    
  • 映射文件

    <select id="findStudent" resultMap="StudentResult" flushCache="false" useCache="true"> <!-- 入参类型是RowBounds,定义不了 --> <!--select 默认不清掉缓存、要使用缓存,这俩配置是一样的;其他操作默认清掉缓存-->
         select * from t_student
    </select>
    
  • 测试

    @Test
    public void testFindStudent() {
        logger.info("逻辑分页查询学生");
        //offset就是start,起始下标
        int offset=0,limit=3;
        //Mybatis的逻辑分页:全部查出来放在内存里,然后只读三条,性能差
        RowBounds rowBounds=new RowBounds(offset,limit);
        List<Student> studentList=studentMapper.findStudent(rowBounds);
        for(Student student:studentList) {
            System.out.println(student);
        }
    }
    
  • 物理分页:真正的分页

    /**
     * 操作数据库的接口
     * @author 14103
     */
    public interface StudentMapper {
        //物理分页
        public List<Student> findStudent2(Map<String, Object> map);
    }
    
  • <!-- sql原生物理分页 -->
     <select id="findStudent2" parameterType="Map" resultMap="StudentResult">
         select * from t_student
         <if test="start!=null and size!=null">
             limit #{start},#{size}
         </if>
     </select>
    
  • @Test
    public void testFindStudent2() {
        logger.info("物理分页查询学生");
        Map<String,Object> map=new HashMap<>();
        map.put("start", 3);
        map.put("size", 3);
        List<Student> studentList=studentMapper.findStudent2(map);
        for(Student student:studentList) {
            System.out.println(student);
        }
    }
    
    缓存
  • 用于并发量很大的查询操作,但要求服务器内存要高。这样可以减轻数据库压力,提高性能。
  • Mybatis默认情况下启用一级缓存,即同一个SqlSession接口对象调用了相同的select语句,会直接从缓存中返回结果,而不是再查询一次数据库。
  • 在映射文件中配置缓存

    <!-- 
        配置缓存:
        1、size:表示缓存cache中能容纳的最大元素数。默认是1024;
        2、flushInterval:定义缓存刷新周期,以毫秒计;
         3、eviction:定义缓存的移除机制;默认是LRU(least recently userd,最近最少使用),还有FIFO(first in first out,先进先出);这俩是Mybatis的算法,推荐前者
         4、readOnly:默认值是false,假如是true的话,缓存只能读(select是读,其他是写,但实际也只有select需要用到缓存)。
     -->
    <cache size="1024" flushInterval="60000" eviction="LRU" readOnly="false"/>
    
    <select id="findStudent" resultMap="StudentResult" flushCache="false" useCache="true"> <!-- 入参类型是RowBounds,定义不了 --> <!--select 默认不清掉缓存、要使用缓存,这俩配置是一样的;其他操作默认清掉缓存-->
         select * from t_student
     </select>
    

    sql注解

    CRUD
  • 使用注解方便灵活,但支持的功能较xml要少,而且代码重用性不高,所以目前还是xml比较常用
  • 直接在数据库接口用注解绑定sql语句,不需要在映射文件绑定

    /**
     * 操作数据库的接口
     * @author 14103
     */
    public interface StudentMapper {
        @Insert("insert into t_student values(#{id},#{name},#{age},null,null,null,null)")
        public int insert(Student student);
    
        @Update("update t_student set name=#{name},age=#{age} where id=#{id}")
        public int update(Student student);
    
        @Delete("delete from t_student where id=#{id}")
        public int delete(int id);
    
        @Select("select * from t_student where id=#{id}")
        public Student getStudentById(Integer id);
    
        @Select("select * from t_student")
        //结果集合映射
        @Results(
                {
                    @Result(id=true,column="id",property="id"),
                    @Result(column="name",property="name"),
                    @Result(column="age",property="age")
                }
        )
        public List<Student> findStudent();
    }
    
    一对一
  • 在地址类的数据层接口写个查找地址的方法

    @Select("select * from t_address where id=#{id}")
    public Address findById(Integer id);
    
  • 在学生类的数据层接口写个查找学生带地址的方法

    @Select("select * from t_student where id=#{id}")
    @Results(
            {
                @Result(id=true,column="id",property="id"),
                @Result(column="name",property="name"),
                @Result(column="age",property="age"),
                @Result(column="addressId",property="address",one=@One(select="com.java1234.mappers.AddressMapper.findById"))
            }
    )
    public Student selectStudentWithAddress(int id);
    
    一对多
  • 在年级类的数据层接口里写个查询年级带学生的方法

    @Select("select * from t_grade where id=#{id}")
    @Results(
            {
                @Result(id=true,column="id",property="id"),
                @Result(column="gradeName",property="gradeName"),
                @Result(column="id",property="students",many=@Many(select="com.java1234.mappers.StudentMapper.selectStudentByGradeId"))
            }
    )
    public Grade findById(Integer id);
    
  • 在学生类的数据层接口里写个根据年级查找学生的方法

    @Select("select * from t_student where gradeId=#{gradeId}")
    @Results(
            {
                @Result(id=true,column="id",property="id"),
                @Result(column="name",property="name"),
                @Result(column="age",property="age"),
                @Result(column="addressId",property="address",one=@One(select="com.java1234.mappers.AddressMapper.findById"))
            }
    )
    public Student selectStudentByGradeId(int gradeId);
    
  • 现在查询年级就能带学生了,再实现查询学生带年级的双向关联
  • 在学生类的数据层接口写个查询学生带年级的方法

    @Select("select * from t_student where id=#{id}")
    @Results(
            {
                @Result(id=true,column="id",property="id"),
                @Result(column="name",property="name"),
                @Result(column="age",property="age"),
                @Result(column="addressId",property="address",one=@One(select="com.java1234.mappers.AddressMapper.findById")),
                @Result(column="gradeId",property="grade",one=@One(select="com.java1234.mappers.GradeMapper.findById"))
            }
    )
    public Student selectStudentWithAddressAndGrade(int id);
    
  • 代码实例:MybatisSqlAnnotation
    动态sql
  • 需要对数据层接口进行实现,但不是通过xml映射文件,而是写一个实现类
  • 首先是数据层接口

    public interface StudentMapper {
    
        @InsertProvider(type=StudentDynaSqlProvider.class,method="insertStudent") //调用了insertStudent方法就会自动把参数传进去,无论insertStudent有无定义入参
        public int insertStudent(Student student);
    
        @UpdateProvider(type=StudentDynaSqlProvider.class,method="updateStudent")
        public int updateStudent(Student student);
    
        @DeleteProvider(type=StudentDynaSqlProvider.class,method="deleteStudent")
        public int deleteStudent(int id);
    
        @SelectProvider(type=StudentDynaSqlProvider.class,method="getStudentById")
        public Student getStudentById(Integer id);
    
        @SelectProvider(type=StudentDynaSqlProvider.class,method="findStudents")
        public List<Student> findStudents(Map<String,Object> map);
    
    }
    
  • 实现类(和接口在同在mappers包下)

    public class StudentDynaSqlProvider { //类名一般都这样取
    
        public String insertStudent(final Student student){ //就是得final,这个入参可以写可以不写,但如果是要直接用student.getName()而不是占位符的话就要写
            //返回一个动态拼接完的sql
            return new SQL(){
                {
                    INSERT_INTO("t_student");
                    if(student.getName()!=null){
                        VALUES("name", "#{name}"); //也可以直接写student.getName(),但是这样写比较好
                    }
                    if(student.getAge()!=null){
                        VALUES("age", "#{age}");
                    }
                }
            }.toString();
        }
    
        public String updateStudent(final Student student){
            return new SQL(){
                {
                    UPDATE("t_student");
                    if(student.getName()!=null){
                        SET("name=#{name}");
                    }
                    if(student.getAge()!=null){
                        SET("age=#{age}");
                    }
                    WHERE("id=#{id}");
                }
            }.toString();
        }
    
        public String deleteStudent(){
            return new SQL(){
                {
                    DELETE_FROM("t_student");
                    WHERE("id=#{id}");
                }
            }.toString();
        }
    
        public String getStudentById(){
            return new SQL(){
                {
                    SELECT("*");
                    FROM("t_student");
                    WHERE("id=#{id}");
                }
            }.toString();
        }
    
        public String findStudents(final Map<String,Object> map){
            return new SQL(){
                {
                    SELECT("*");
                    FROM("t_student");
                    StringBuffer sb=new StringBuffer();
                    if(map.get("name")!=null){
                        sb.append(" and name like '"+map.get("name")+"'"); //name属性就要自己带上俩%;注:+两边从内到外是一个双引号一个单引号
                    }
                    if(map.get("age")!=null){
                        sb.append(" and age="+map.get("age"));
                    }
                    //什么条件都没有就不要拼where语句了
                    if(!sb.toString().equals("")){
                        //去掉第一个子句的and
                        WHERE(sb.toString().replaceFirst("and", ""));                    
                    }
                }
            }.toString();
        }
    }
    
  • 这种用得也是比较少