Mybatis-基础入门

概述

Mybatis是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它的数据库无关性较低

什么是ORM
Object Relation Mapping,对象关系映射。对象指的是Java对象,关系指的是数据库中的关系模型,对象关系映射,指的就是在Java对象和数据库的关系模型之间建立一种对应关系,比如用一个Java的Student类,去对应数据库中的一张student表,类中的属性和表中的列一一对应。Student类就对应student表,一个Student对象就对应student表中的一行数据

为什么mybatis是半自动的ORM框架
用mybatis进行开发,需要手动编写SQL语句。而全自动的ORM框架,如hibernate,则不需要编写SQL语句。用hibernate开发,只需要定义好ORM映射关系,就可以直接进行CRUD操作了。由于mybatis需要手写SQL语句,所以它有较高的灵活性,可以根据需要,自由地对SQL进行定制,也因为要手写SQL,当要切换数据库时,SQL语句可能就要重写,因为不同的数据库有不同的方言(Dialect),所以mybatis的数据库无关性低。虽然mybatis需要手写SQL,但相比JDBC,它提供了输入映射和输出映射,可以很方便地进行SQL参数设置,以及结果集封装。并且还提供了关联查询和动态SQL等功能,极大地提升了开发的效率。并且它的学习成本也比hibernate低很多

快速入门

只需要通过如下几个步骤,即可用mybatis快速进行持久层的开发

  • 编写全局配置文件
  • 编写mapper映射文件
  • 加载全局配置文件,生成SqlSessionFactory
  • 创建SqlSession,调用mapper映射文件中的SQL语句来执行CRUD操作
原生开发示例
  1. 在本地虚拟机mysql上创建一个库yogurt,并在里面创建一张student表

  2. 打开IDEA,创建一个maven项目

  3. 导入依赖的jar包

  4. 创建一个po类

  5. 编写mapper映射文件(编写SQL)

    <!-- 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">
    
    <mapper namespace="test">
        <select id="findAll" resultType="com.yogurt.po.Student">
            SELECT * FROM student;
        </select>
    
        <insert id="insert" parameterType="com.yogurt.po.Student">
            INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
        </insert>
        
        <delete id="delete" parameterType="int">
            DELETE FROM student WHERE id = #{id};
        </delete>
    </mapper>
    
    
  6. 编写数据源properties文件

    db.url=jdbc:mysql://192.168.183.129:3306/yogurt?characterEncoding=utf8
    db.user=root
    db.password=root
    db.driver=com.mysql.jdbc.Driver
    
  7. 编写全局配置文件(主要是配置数据源信息)

    <?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="properties/db.properties"></properties>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <!-- 从配置文件中加载属性 -->
                    <property name="driver" value="${db.driver}"/>
                    <property name="url" value="${db.url}"/>
                    <property name="username" value="${db.user}"/>
                    <property name="password" value="${db.password}"/>
                </dataSource>
            </environment>
        </environments>
    
        <mappers>
            <!-- 加载前面编写的SQL语句的文件 -->
            <mapper resource="StudentMapper.xml"/>
        </mappers>
    
    </configuration>
    
    
  8. 编写dao类

    public class StudentDao {
    
    	private SqlSessionFactory sqlSessionFactory;
    
    	public StudentDao(String configPath) throws IOException {
    		InputStream inputStream = Resources.getResourceAsStream(configPath);
    		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    	}
    
    	public List<Student> findAll() {
    		SqlSession sqlSession = sqlSessionFactory.openSession();
    		List<Student> studentList = sqlSession.selectList("findAll");
    		sqlSession.close();
    		return studentList;
    	}
    
    	public int addStudent(Student student) {
    		SqlSession sqlSession = sqlSessionFactory.openSession();
    		int rowsAffected = sqlSession.insert("insert", student);
    		sqlSession.commit();
    		sqlSession.close();
    		return rowsAffected;
    	}
    
    	public int deleteStudent(int id) {
    		SqlSession sqlSession = sqlSessionFactory.openSession();
    		int rowsAffected = sqlSession.delete("delete",id);
    		sqlSession.commit();
    		sqlSession.close();
    		return rowsAffected;
    	}
    }
    
  9. 测试

    public class SimpleTest {
    
    	private StudentDao studentDao;
    
    	@Before
    	public void init() throws IOException {
    		studentDao = new StudentDao("mybatis-config.xml");
    	}
    
    	@Test
    	public void insertTest() {
    		Student student = new Student();
    		student.setName("yogurt");
    		student.setAge(24);
    		student.setGender(1);
    		student.setScore(100);
    		studentDao.addStudent(student);
    	}
    
    	@Test
    	public void findAllTest() {
    		List<Student> all = studentDao.findAll();
    		all.forEach(System.out::println);
    	}
    }
    
    

注意要点

全局配置文件中,各个标签要按照如下顺序进行配置,因为mybatis加载配置文件的源码中是按照这个顺序进行解析的

<configuration>
	<!-- 配置顺序如下
     properties  

     settings

     typeAliases

     typeHandlers

     objectFactory

     plugins

     environments
        environment
            transactionManager
            dataSource

     mappers
     -->
</configuration>
  1. <properties>: 一般将数据源的信息单独放在一个properties文件中,然后用这个标签引入,在下面environment标签中,就可以用${}占位符快速获取数据源的信息

  2. <settings>: 用来开启或关闭mybatis的一些特性,比如可以用<setting name="lazyLoadingEnabled" value="true"/>来开启延迟加载,可以用<settings name="cacheEnabled" value="true"/>来开启二级缓存

  3. <typeAliases>: 在mapper.xml中需要使用parameterType和resultType属性来配置SQL语句的输入参数类型和输出参数类型,类必须要写上全限定名,比如一个SQL的返回值映射为Student类,则resultType属性要写com.yogurt.po.Student,这太长了,所以可以用别名来简化书写,比如:

    <typeAliases>
        <typeAlias type="com.yogurt.po.Student" alias="student"/>
    </typeAliases>
    

    之后就可以在resultType上直接写student,mybatis会根据别名配置自动找到对应的类。
    当然,如果想要一次性给某个包下的所有类设置别名,可以用如下的方式:

    <typeAliases>
       <package name="com.yogurt.po"/>
    </typeAliases>
    

    如此,指定包下的所有类,都会以简单类名的小写形式,作为它的别名
    另外,对于基本的Java类型 -> 8大基本类型以及包装类,以及String类型,mybatis提供了默认的别名,别名为其简单类名的小写,比如原本需要写java.lang.String,其实可以简写为string

  4. <typeHandlers>: 用于处理Java类型和Jdbc类型之间的转换,mybatis有许多内置的TypeHandler,比如StringTypeHandler,会处理Java类型 String和Jdbc类型CHAR和VARCHAR。这个标签用的不多

  5. <objectFactory>: mybatis会根据resultType或resultMap的属性来将查询得到的结果封装成对应的Java类,它有一个默认的DefaultObjectFactory,用于创建对象实例,这个标签用的也不多

  6. <plugins>: 可以用来配置mybatis的插件,比如在开发中经常需要对查询结果进行分页,就需要用到pageHelper分页插件,这些插件就是通过这个标签进行配置的。在mybatis底层,运用了责任链模式+动态代理去实现插件的功能

    <!-- PageHelper 分页插件 -->
    <plugins>
      <plugin interceptor="com.github.pagehelper.PageInterceptor">
         <property name="helperDialect" value="mysql"/>
      </plugin>
    </plugins>
    
  7. <environments>: 用来配置数据源

  8. <mappers>: 用来配置mapper.xml映射文件,这些xml文件里都是SQL语句

mapper.xml的SQL语句中的占位符${}#{}
一般会采用#{}#{}在mybatis中,最后会被解析为?,其实就是Jdbc的PreparedStatement中的?占位符,它有预编译的过程,会对输入参数进行类型解析(如果入参是String类型,设置参数时会自动加上引号),可以防止SQL注入,如果parameterType属性指定的入参类型是简单类型的话(简单类型指的是8种java原始类型再加一个String),#{}中的变量名可以任意,如果入参类型是pojo,比如是Student类那么#{name}表示取入参对象Student中的name属性,#{age}表示取age属性,这个过程是通过反射来做的,这不同于${}${}取对象的属性使用的是OGNL(Object Graph Navigation Language)表达式

${},一般会用在模糊查询的情景,比如SELECT * FROM student WHERE name like '%${name}%';
它的处理阶段在#{}之前,它不会做参数类型解析,而仅仅是做了字符串的拼接,若入参的Student对象的name属性为zhangsan,则上面那条SQL最终被解析为SELECT * FROM student WHERE name like '%zhangsan%';
而如果此时用的是SELECT * FROM student WHERE name like '%#{name}%';这条SQL最终就会变成SELECT * FROM student WHERE name like '%'zhangsan'%';所以模糊查询只能用${},虽然普通的入参也可以用${},但由于${}不会做类型解析,就存在SQL注入的风险,比如SELECT * FROM user WHERE name = '${name}' AND password = '${password}'
我可以让一个user对象的password属性为’OR ‘1’ = '1,最终的SQL就变成了SELECT * FROM user WHERE name = 'yogurt' AND password = ''OR '1' = '1',因为OR '1' = '1'恒成立,这样攻击者在不需要知道用户名和密码的情况下,也能够完成登录验证

另外,对于pojo的入参,${}中获取对象属性的语法和#{}几乎一样,但${}在mybatis底层是通过OGNL表达式语言进行处理的,这跟#{}的反射处理有所不同

对于简单类型(8种java原始类型再加一个String)的入参,${}中参数的名字必须是value,例子如下

<select id="fuzzyCount" parameterType="string" resultType="int">
        SELECT count(1) FROM `user` WHERE name like '%${value}%'
</select>

为什么简单类型的变量名必须为value呢?因为mybatis源码中写死的value

上面其实是比较原始的开发方式,我们需要编写dao类,针对mapper.xml中的每个SQL标签,做一次封装,SQL标签的id要以字符串的形式传递给SqlSession的相关方法,容易出错,非常不方便;为了简化开发,mybatis提供了mapper接口代理的开发方式,不需要再编写dao类,只需要编写一个mapper接口,一个mapper的接口和一个mapper.xml相对应,只需要调用SqlSession对象上的getMapper(),传入mapper接口的class信息,即可获得一个mapper代理对象,直接调用mapper接口中的方法,即相当于调用mapper.xml中的各个SQL标签,此时就不需要指定SQL标签的id字符串了,mapper接口中的一个方法,就对应了mapper.xml中的一个SQL标签

基于Mapper代理的示例

全局配置文件和mapper.xml文件是最基本的配置,仍然需要。不过,这次我们不编写dao类,我们直接创建一个mapper接口

package com.yogurt.mapper;

import com.yogurt.po.Student;

import java.util.List;

public interface StudentMapper {

	List<Student> findAll();

	int insert(Student student);

	int delete(Integer id);

	List<Student> findByName(String value);
}

而我们的mapper.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.yogurt.mapper.StudentMapper">
    <select id="findAll" resultType="com.yogurt.po.Student">
        SELECT * FROM student;
    </select>

    <insert id="insert" parameterType="com.yogurt.po.Student">
        INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
    </insert>

    <delete id="delete" parameterType="int">
        DELETE FROM student WHERE id = #{id};
    </delete>

    <select id="findByName" parameterType="string" resultType="student">
        SELECT * FROM student WHERE name like '%${value}%';
    </select>
</mapper>

public class MapperProxyTest {
	private SqlSessionFactory sqlSessionFactory;

	@Before
	public void init() throws IOException {
		InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
	}

	@Test
	public void test() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
		List<Student> studentList = mapper.findAll();
		studentList.forEach(System.out::println);
	}
}

mapper接口和mapper.xml之间需要遵循一定规则,才能成功的让mybatis将mapper接口和mapper.xml绑定起来

  • mapper接口的全限定名,要和mapper.xml的namespace属性一致
  • mapper接口中的方法名要和mapper.xml中的SQL标签的id一致
  • mapper接口中的方法入参类型,要和mapper.xml中SQL语句的入参类型一致
  • mapper接口中的方法出参类型,要和mapper.xml中SQL语句的返回值类型一致
基于注解的示例

如果实在看xml配置文件不顺眼,则可以考虑使用注解的开发方式,不过注解的开发方式,会将SQL语句写到代码文件中,后续的维护性和扩展性不是很好(如果想修改SQL语句,就得改代码,得重新打包部署,而如果用xml方式,则只需要修改xml,用新的xml取替换旧的xml即可)

使用注解的开发方式,也还是得有一个全局配置的xml文件,不过mapper.xml就可以省掉了,具体操作只用2步,如下
创建一个Mapper接口:

package com.yogurt.mapper;
import com.yogurt.po.Student;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import java.util.List;

public interface PureStudentMapper {

	@Select("SELECT * FROM student")
	List<Student> findAll();

	@Insert("INSERT INTO student (name,age,score,gender) VALUES (#{name},#{age},#{score},#{gender})")
	int insert(Student student);
}

在全局配置文件中修改<mappers>标签,直接指定加载这个类

<?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="properties/db.properties"></properties>
    <typeAliases>
        <package name="com.yogurt.po"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${db.driver}"/>
                <property name="url" value="${db.url}"/>
                <property name="username" value="${db.user}"/>
                <property name="password" value="${db.password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper class="com.yogurt.mapper.PureStudentMapper"/>
    </mappers>

</configuration>

测试代码如下

public class PureMapperTest {

	private SqlSessionFactory sqlSessionFactory;

	@Before
	public void init() throws IOException {
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	}

	@Test
	public void test() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		PureStudentMapper mapper = sqlSession.getMapper(PureStudentMapper.class);
		mapper.insert(new Student(10,"Tomcat",120,60,0));
        sqlSession.commit();
		List<Student> studentList = mapper.findAll();
		studentList.forEach(System.out::println);
	}
}

若需要传入多个参数,可以结合@Param注解,示例如下

package org.mybatis.demo.mapper;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.mybatis.demo.po.Student;

import java.util.List;

public interface PureStudentMapper {

	@Select("SELECT * FROM student WHERE name like '%${name}%' AND major like '%${major}%'")
	List<Student> find(@Param("name") String name, @Param("major") String major);
}

@Param标签会被mybatis处理并封装成一个Map对象,比如上面的示例中,实际传入的参数是一个Map对象,@Param标签帮忙向Map中设置了值,即它做了:

Map<String,Object> map = new HashMap<>();
map.put("name", name);
map.put("major",major);

将方法形参中的name和major放到了map对象中,所以在@Select标签中可以用${name}${major}取出map对象中的值。

加载Mapper的方式

上面我们见到了在全局配置文件中,两种配置mapper的方式,分别是:

<!-- 在mapper接口中使用注解 -->
<mappers>
    <mapper class="com.yogurt.mapper.PureStudentMapper"/>
</mappers>

<!-- 普通加载xml -->
<mappers>
    <mapper resource="StudentMapper.xml"/>
</mappers>

而在实际工作中,一般我们会将一张表的SQL操作封装在一个mapper.xml中,可能有许多张表需要操作,那么我们是不是要在<mappers>标签下写多个<mapper>标签呢?其实不用,还有第三种加载mapper的方法,使用<package>标签

<mappers>
    <package name="com.yogurt.mapper"/>
</mappers>

这样就会自动加载com.yogurt.mapper包下的所有mapper,这种方式需要将mapper接口文件和mapper.xml文件都放在com.yogurt.mapper包下,且接口文件和xml文件的文件名要一致。注意,在IDEA的maven开发环境下,maven中还需配置<resources>标签,否则maven打包不会将java源码目录下的xml文件打包进去,见下文

三种加载mapper的方式总结
<mapper resource="" />

  • 加载普通的xml文件,传入xml的相对路径(相对于类路径)

<mapper class="" />

  • 使用mapper接口的全限定名来加载,若mapper接口采用注解方式,则不需要xml;若mapper接口没有采用注解方式,则mapper接口和xml文件的名称要相同,且在同一个目录

<package name="" />

  • 扫描指定包下的所有mapper,若mapper接口采用注解方式,则不需要xml;若mapper接口没有采用注解方式,则mapper接口和xml文件的名称要相同,且在同一目录

注意:用后两种方式加载mapper接口和mapper.xml映射文件时,可能会报错
在这里插入图片描述
仔细检查了一下,mapper接口文件和xml映射文件确实放在了同一个目录下,而且文件名一致,xml映射文件的namespace也和mapper接口的全限定名对的上。为什么会这样呢?
其实是因为,对于src/main/java 源码目录下的文件,maven打包时只会将该目录下的java文件打包,而其他类型的文件都不会被打包进去,去工程目录的target目录下看看maven构建后生成的文件

我们需要在pom.xml中的 标签下 添加<resources>标签,指定打包时要将xml文件打包进去

<build>
	<resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
</build>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值