MyBatis-从JDBC到Spring整合MyBatis

本文所用代码下载地址:spring-mybatis

1.MyBatis基础

1.JDBC连接数据库

  1. 注册驱动,获取连接
  2. 创建Statement对象
  3. execute()方法执行sql
  4. 把结果集转成POJO对象
  5. 关闭资源

下面以一个简单的例子来介绍:
以本地数据库来测试:在这里插入图片描述

	@Test
    public void testJdbc() throws IOException {
        Connection conn = null;
        Statement stmt = null;
        Blog blog = new Blog();
        try {
            // 注册 JDBC 驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 打开连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatisdemo", "mybatistest", "123456");
            // 执行查询
            stmt = conn.createStatement();
            String sql = "SELECT bid, name, author_id FROM blog where bid = 1";
            ResultSet rs = stmt.executeQuery(sql);
            // 获取结果集
            while (rs.next()) {
                Integer bid = rs.getInt("bid");
                String name = rs.getString("name");
                Integer authorId = rs.getInt("author_id");
                blog.setAuthorId(authorId);
                blog.setBid(bid);
                blog.setName(name);
            }
            System.out.println(blog);
            rs.close();
            stmt.close();
            conn.close();
        } catch (SQLException se) {
            se.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (stmt != null) stmt.close();
            } catch (SQLException se2) {
            }
            try {
                if (conn != null) conn.close();
            } catch (SQLException se) {
                se.printStackTrace();
            }
        }
    }

运行结果:

在这里插入图片描述

这里只是查询了表中的一条数据,但是在实际业务中可能有各种的select /update/delete/insert等操作,每次都需要 完成 注册驱动,获取连接,创建Statement对象这些重复的动作。特别是忘记写close的代码,很有可能会造成数据库连接资源耗尽。并且sql语句是直接耦合在业务代码中,如果业务逻辑有修改或数据库环境变化,又需要对业务代码进行修改,维护起来就非常的困难了。综合以上,我们很少用JDBC去直接访问数据库。

2.Spring JDBC

  1. 实现RowMapper接口,mapRow()方法
  2. 转换结果集返回Object
public List<Employee> query(String sql){
	new JdbcTemplate(new DruidDataSource());
	return jdbcTemplate.query(sql,new EmployeeRowMapper())
}

EmployeeRowMapper:

public class EmployeeRowMapper implements RowMapper {
    @Override
    public Object mapRow(ResultSet resultSet, int i) throws SQLException {
        Employee employee = new Employee();
        employee.setEmpId(resultSet.getInt("emp_id"));
        employee.setEmpName(resultSet.getString("emp_name"));
        employee.setGender(resultSet.getString("gender"));
        employee.setEmail(resultSet.getString("email"));
        return employee;
    }
}

测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class EmployeeRowMapperTest {

    @Autowired
    JdbcTemplate jdbcTemplate;

    List<Employee> list;

    @Test
    public void EmployeeTest() {
        list = jdbcTemplate.query("select * from tbl_emp", new EmployeeRowMapper());
        System.out.println(list);
    }
}

在这里插入图片描述

但是这种方法每一个实体类都需要一个Mapper类,也显得有些繁琐。那么,有没有一种方法可以使数据库的字段跟实体类的字段自动转换呢?

这里定义了一个通用的BaseRowMapper类,通过反射来实现

public class BaseRowMapper<T> implements RowMapper<T> {

    private Class<?> targetClazz;
    private HashMap<String, Field> fieldMap;

    public BaseRowMapper(Class<?> targetClazz) {
        this.targetClazz = targetClazz;
        fieldMap = new HashMap<>();
        Field[] fields = targetClazz.getDeclaredFields();
        for (Field field : fields) {
            fieldMap.put(field.getName(), field);
        }
    }

    @Override
    public T mapRow(ResultSet rs, int arg1) throws SQLException {
        T obj = null;

        try {
            obj = (T) targetClazz.newInstance();

            final ResultSetMetaData metaData = rs.getMetaData();
            int columnLength = metaData.getColumnCount();
            String columnName = null;

            for (int i = 1; i <= columnLength; i++) {
                columnName = metaData.getColumnName(i);
                Class fieldClazz = fieldMap.get(camel(columnName)).getType();
                Field field = fieldMap.get(camel(columnName));
                field.setAccessible(true);

                // fieldClazz == Character.class || fieldClazz == char.class
                if (fieldClazz == int.class || fieldClazz == Integer.class) { // int
                    field.set(obj, rs.getInt(columnName));
                } else if (fieldClazz == boolean.class || fieldClazz == Boolean.class) { // boolean
                    field.set(obj, rs.getBoolean(columnName));
                } else if (fieldClazz == String.class) { // string
                    field.set(obj, rs.getString(columnName));
                } else if (fieldClazz == float.class) { // float
                    field.set(obj, rs.getFloat(columnName));
                } else if (fieldClazz == double.class || fieldClazz == Double.class) { // double
                    field.set(obj, rs.getDouble(columnName));
                } else if (fieldClazz == BigDecimal.class) { // bigdecimal
                    field.set(obj, rs.getBigDecimal(columnName));
                } else if (fieldClazz == short.class || fieldClazz == Short.class) { // short
                    field.set(obj, rs.getShort(columnName));
                } else if (fieldClazz == Date.class) { // date
                    field.set(obj, rs.getDate(columnName));
                } else if (fieldClazz == Timestamp.class) { // timestamp
                    field.set(obj, rs.getTimestamp(columnName));
                } else if (fieldClazz == Long.class || fieldClazz == long.class) { // long
                    field.set(obj, rs.getLong(columnName));
                }

                field.setAccessible(false);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return obj;
    }

    /**
     * 下划线转驼峰
     * @param str
     * @return
     */
    public static String camel(String str) {
        Pattern pattern = Pattern.compile("_(\\w)");
        Matcher matcher = pattern.matcher(str);
        StringBuffer sb = new StringBuffer(str);
        if(matcher.find()) {
            sb = new StringBuffer();
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
            matcher.appendTail(sb);
        }else {
            return sb.toString();
        }
        return camel(sb.toString());
    }
}

测试代码:

	@Test
    public void EmployeeTest() {
        list = jdbcTemplate.query(" select * from tbl_emp", new BaseRowMapper(Employee.class));
        System.out.println(list);
    }

可以看到,效果是一样的

在这里插入图片描述

总结:

工具类解决了以下问题:

  • 方法封装
    在这里插入图片描述

  • 支持数据源,不用手动创建和关闭

// 支持传入DataSource
public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
        this.setDataSource(dataSource);
        this.setLazyInit(lazyInit);
        this.afterPropertiesSet();
    }
  • 映射结果集

未解决问题:

  • SQL语句硬编码
  • 参数只能按顺序传入(占位符)
  • 没有实现实体类到数据库记录的映射
  • 没有提供缓存等功能。

2. ORM

在这里插入图片描述

mybatis特性:

  1. 使用连接池对连接进行管理
  2. SQL和代码分离,集中管理(mapper.xml)
  3. 参数映射和动态SQL
  4. 结果集映射
  5. 缓存管理
  6. 重复SQL提取<sql>
  7. 插件机制

如何选择ORM框架??

  1. 业务简单的项目可以用Hibernate
  2. 需要灵活的SQL,可以用MyBatis
  3. 对性能要求高,可以使用JDBC
  4. Spring JDBC可以和ORM框架混用

3. MyBatis应用

3.1 MyBatis工作原理

在这里插入图片描述

  1. 读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。

  2. 加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。

  3. 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。

  4. 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。

  5. Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。

  6. MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。

  7. 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。

  8. 输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

3.2 Spring 集成Mybatis

3.2.1 引入相关的jar(pom.xml)

		<!--mybatis 和Spring整合 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${mybatis-spring.version}</version>
        </dependency>

3.2.2 创建sqlSessionFactory (applicationContext.xml)

	<!-- 在Spring启动时创建 sqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    	<!-- mybatis配置文件 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
        <!-- 映射器文件 -->
        <property name="mapperLocations" value="classpath:/*.xml"></property>
        <!-- Spring 连接池 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

Spring的连接池

		<!-- Spring的连接池 -->
       <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="${driverClassName}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </bean>

mybatis配置文件

<?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>
    <!-- 参数设置 -->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 打印查询语句 -->
        <setting name="logImpl" value="STDOUT_LOGGING" />
        <!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false  -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签的 fetchType来覆盖-->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!--  Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认JAVASSIST -->
        <setting name="proxyFactory" value="CGLIB" />
    </settings>

    <!-- 类型别名 -->
    <!--    <typeAliases>
            <package name="com.liulin.mybatis.crud.bean"/>
    &lt;!&ndash;
            <typeAlias alias="Department" type="Department" />
    &ndash;&gt;
        </typeAliases>-->

    <!--分页插件的注册-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- 4.0.0以后版本可以不设置该参数 ,可以自动识别
            <property name="dialect" value="mysql"/>  -->
            <!-- 该参数默认为false -->
            <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
            <!-- 和startPage中的pageNum效果一样-->
            <property name="offsetAsPageNum" value="true"/>
            <!-- 该参数默认为false -->
            <!-- 设置为true时,使用RowBounds分页会进行count查询 -->
            <property name="rowBoundsWithCount" value="true"/>
            <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
            <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
            <property name="pageSizeZero" value="true"/>
            <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
            <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
            <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
            <property name="reasonable" value="true"/>
            <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
            <!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
            <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值 -->
            <!-- 不理解该含义的前提下,不要随便复制该配置 -->
            <property name="params" value="pageNum=start;pageSize=limit;"/>
            <!-- 支持通过Mapper接口参数来传递分页参数 -->
            <property name="supportMethodsArguments" value="true"/>
            <!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
            <property name="returnPageInfo" value="check"/>
        </plugin>
    </plugins>
</configuration>

映射器文件

<?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.liulin.mybatis.dao.DepartmentMapper">
    <resultMap id="BaseResultMap" type="com.liulin.mybatis.bean.Department">
        <id column="dept_id" jdbcType="INTEGER" property="deptId"/>
        <result column="dept_name" jdbcType="VARCHAR" property="deptName"/>
    </resultMap>
    <sql id="Base_Column_List">
        dept_id, dept_name
    </sql>

    <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from tbl_dept
        where dept_id = #{deptId,jdbcType=INTEGER}
    </select>

    <select id="selectByMap" parameterType="java.util.Map" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        FROM tbl_dept e
        where 1=1
        <if test="did != null">
            and d_id = #{did,jdbcType=INTEGER}
        </if>
    </select>

    <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    delete from tbl_dept
    where dept_id = #{deptId,jdbcType=INTEGER}
  </delete>

    <insert id="insert" parameterType="com.liulin.mybatis.bean.Department">
    insert into tbl_dept (dept_id, dept_name)
    values (#{deptId,jdbcType=INTEGER}, #{deptName,jdbcType=VARCHAR})
  </insert>

    <insert id="insertSelective" parameterType="com.liulin.mybatis.bean.Department">
        insert into tbl_dept
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="deptId != null">
                dept_id,
            </if>
            <if test="deptName != null">
                dept_name,
            </if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="deptId != null">
                #{deptId,jdbcType=INTEGER},
            </if>
            <if test="deptName != null">
                #{deptName,jdbcType=VARCHAR},
            </if>
        </trim>
    </insert>

    <update id="updateByPrimaryKeySelective" parameterType="com.liulin.mybatis.bean.Department">
        update tbl_dept
        <set>
            <if test="deptName != null">
                dept_name = #{deptName,jdbcType=VARCHAR},
            </if>
        </set>
        where dept_id = #{deptId,jdbcType=INTEGER}
    </update>

    <update id="updateByPrimaryKey" parameterType="com.liulin.mybatis.bean.Department">
    update tbl_dept
    set dept_name = #{deptName,jdbcType=VARCHAR}
    where dept_id = #{deptId,jdbcType=INTEGER}
  </update>
</mapper>

3.2.3 配置扫描器,将mybatis的接口实现加入到 IOC容器中(applicationContext.xml)

方法1:

    <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.liulin.mybatis.dao"/>
    </bean>

方法2:

	<mybatis-spring:scan base-package="com.liulin.mybatis.dao"/>

启动项目,就可以对员工信息进行增删改查操作了
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值