Java

Spring学习笔记(四): Spring 数据库编程

2019-03-24  本文已影响1人  简单一点点

Spring 为数据库编程提供了 JDBC 模板,允许简化许多代码,但实际中并不常用,因此只做了解。

全部章节传送门:
Spring学习笔记(一):Spring IoC 容器
Spring学习笔记(二):Spring Bean 装配
Spring学习笔记(三): Spring 面向切面
Spring学习笔记(四): Spring 数据库编程
Spring学习笔记(五): Spring 事务管理

传统 JDBC 代码

首先总结一下传统 JDBC 操作。

为了测试数据库操作,首先在数据库中插入一个表,并添加数据。

create table t_role(
    id int auto_increment primary key,
    role_name varchar(20),
    note varchar(50)
);

insert into t_role(role_name, note) values('warrior','Warriors can use all kinds of weapons skillfully');

传统 JDBC 回顾

创建 MAVEN 项目并添加 JDBC 依赖。

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.25</version>
    </dependency>
</dependencies>

创建和数据库中的表相对应的 Role 实体类。

package com.wyk.springdb.domain;

public class Role {
    private Long id;
    private String roleName;
    private String note;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}

然后编写一段传统的 JDBC 查询代码。

package com.wyk.springdb.db;

import com.wyk.springdb.domain.Role;

import java.sql.*;

public class DbOPeration {
    public Role getRole(Long id) {
        Role role = null;
        //声明jdbc变量
        Connection con = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            //注册驱动程序
            Class.forName("com.mysql.jdbc.Driver");
            //获取连接
            con = DriverManager.getConnection("jdbc:mysql://localhost:3306/springstudy",
                    "root", "wanyunkai123");
            //预编译 SQL
            ps = con.prepareStatement("select id, role_name, note from t_role where id  = ?");
            //设置参数
            ps.setLong(1, id);
            //执行SQL
            rs = ps.executeQuery();
            //组合结果返回POJO
            while(rs.next()) {
                role = new Role();
                role.setId(rs.getLong(1));
                role.setRoleName(rs.getString(2));
                role.setNote(rs.getString(3));
            }
        } catch (ClassNotFoundException | SQLException e) {
            //异常处理
            e.printStackTrace();
        } finally {
            //关闭数据库连接资源
            try {
                if(rs != null && (!rs.isClosed())) {
                    rs.close();
                }
            } catch(SQLException e) {
                e.printStackTrace();
            }
            try {
                if(ps != null && !ps.isClosed()) {
                    ps.close();
                }
            } catch(SQLException e) {
                e.printStackTrace();
            }
            try {
                if(con != null && !con.isClosed()) {
                    con.close();
                }
            } catch(SQLException e) {
                e.printStackTrace();
            }
        }
        return role;
    }
}

然后编写代码进行测试。

public class JdbcTest {
    public static void main(String[] args) {
        DbOPeration operation = new DbOPeration();
        Role role = operation.getRole(1L);
        System.out.println("{id: " + role.getId() + ", role_name: "
                + role.getRoleName() + ", note: " + role.getNote() + "}");
    }
}

运行程序可以查询出结果,但是可以看出仅仅是一条简单的查询,代码也很复杂,其中也包含较多的 try...catch...finally... 语句。

优化传统 JDBC

我们可以把重复的模板代码提出来创建一个工具类。

package com.wyk.springdb.util;

import org.omg.CORBA.OBJECT_NOT_EXIST;

import java.sql.*;

public class DBUtil {

    static String ip = "127.0.0.1";
    static int port = 3306;
    static String database = "springstudy";
    static String encoding = "UTF-8";
    static String username = "root";
    static String password = "wanyunkai123";

    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    //建立连接
    public static Connection getConnection() throws SQLException {
        String url = String.format("jdbc:mysql://%s:%d/%s?characterEncoding=%s",
                ip, port, database, encoding);
        return DriverManager.getConnection(url, username, password);
    }

    public static void closeResource(Connection conn, Statement st, ResultSet rs) {
        if(conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(st != null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

这样就可以修改查询类。

package com.wyk.springdb.db;

import com.wyk.springdb.domain.Role;
import com.wyk.springdb.util.DBUtil;

import java.sql.*;

public class DbOPeration2 {

    public Role getRole(Long id) {
        Role role = null;
        //声明jdbc变量
        Connection con = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            con = DBUtil.getConnection();
            //预编译 SQL
            ps = con.prepareStatement("select id, role_name, note from t_role where id  = ?");
            //设置参数
            ps.setLong(1, id);
            //执行SQL
            rs = ps.executeQuery();
            //组合结果返回POJO
            while(rs.next()) {
                role = new Role();
                role.setId(rs.getLong(1));
                role.setRoleName(rs.getString(2));
                role.setNote(rs.getString(3));
            }
        } catch (SQLException e) {
            //异常处理
            e.printStackTrace();
        } finally {
            DBUtil.closeResource(con, ps, rs);
        }
        return role;
    }
}

尽管可以优化一下,但我们会发现传统的 JDBC 还是过于复杂。

配置数据库资源

为了解决 JDBC 的问题, Spring 提供了自己的解决方案,那就是 JdbcTemplate 模板,不过首先需要了解一下如何配置数据库资源。

简单数据库配置

Spring 提供了一个简单的数据库配置 org.springframework.jdbc.datasource.SimpleDriverDataSource,它很简单,不支持数据库连接池,一般用于简单的测试。

<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">      
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/springstudy?characterEncoding=UTF8" />
    <property name="username" value="root" />
    <property name="password" value="wanyunkai123" />
</beans>

### 使用第三方数据库连接池

正式开发中经常使用第三方的数据库连接池,比如 DBCP 数据库连接池。

```xml
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"><!--设置为close使Spring容器关闭同时数据源能够正常关闭,以免造成连接泄露  -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/springstudy?characterEncoding=UTF8" />
    <property name="username" value="root" />
    <property name="password" value="wanyunkai123" />
    <property name="defaultReadOnly" value="false" /><!-- 设置为只读状态,配置读写分离时,读库可以设置为true -->
    <!-- 在连接池创建后,会初始化并维护一定数量的数据库安连接,当请求过多时,数据库会动态增加连接数,
    当请求过少时,连接池会减少连接数至一个最小空闲值 -->
    <property name="initialSize" value="5" /><!-- 在启动连接池初始创建的数据库连接,默认为0 -->
    <property name="maxActive" value="15" /><!-- 设置数据库同一时间的最大活跃连接默认为8,负数表示不闲置 -->
    <property name="maxIdle" value="10"/><!-- 在连接池空闲时的最大连接数,超过的会被释放,默认为8,负数表示不闲置 -->
    <property name="minIdle" value="2" /><!-- 空闲时的最小连接数,低于这个数量会创建新连接,默认为0 -->
    <property name="maxWait" value="10000" /><!-- 连接被用完时等待归还的最大等待时间,单位毫秒,超出时间抛异常,默认为无限等待 -->
</bean>

使用 JNDI 数据库连接池

当数据源配置在 Tomcat 等服务器上的时候,需要通过 JNDI 配置数据源。假设在 Tomcat 上配置了 JNDI 为 jdbc/springdb 的数据源,则 Web 工程中配置如下。

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/springdb" />
</beans>

JdbcTemplate

JdbcTemplate 是 Spring 针对 JDBC 代码失控提供的解决方案,但严格来说,它本身不算成功。但体现了 Spring 框架的主导思想之一: 给与常用技术提供模板化编程,减少开发者工作量。

首先为项目添加完整的配置文件 spring-cfg.xml。其中使用了 DBCP 数据库连接池,需添加 DBCP 依赖。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close"><!--设置为close使Spring容器关闭同时数据源能够正常关闭,以免造成连接泄露  -->
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/springstudy?characterEncoding=UTF8" />
        <property name="username" value="root" />
        <property name="password" value="wanyunkai123" />
        <property name="defaultReadOnly" value="false" /><!-- 设置为只读状态,配置读写分离时,读库可以设置为true -->
        <!-- 在连接池创建后,会初始化并维护一定数量的数据库安连接,当请求过多时,数据库会动态增加连接数,
        当请求过少时,连接池会减少连接数至一个最小空闲值 -->
        <property name="initialSize" value="5" /><!-- 在启动连接池初始创建的数据库连接,默认为0 -->
        <property name="maxActive" value="15" /><!-- 设置数据库同一时间的最大活跃连接默认为8,负数表示不闲置 -->
        <property name="maxIdle" value="10"/><!-- 在连接池空闲时的最大连接数,超过的会被释放,默认为8,负数表示不闲置 -->
        <property name="minIdle" value="2" /><!-- 空闲时的最小连接数,低于这个数量会创建新连接,默认为0 -->
        <property name="maxWait" value="10000" /><!-- 连接被用完时等待归还的最大等待时间,单位毫秒,超出时间抛异常,默认为无限等待 -->
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource" />
    </bean>
</beans>

然后添加数据库操作的测试类。

package com.wyk.springdb.test;

import com.mysql.jdbc.JDBC4CallableStatement;
import com.wyk.springdb.domain.Role;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcTempLateTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cfg.xml");
        JdbcTemplate jdbcTemplate = ctx.getBean(JdbcTemplate.class);
        Long id = 1L;
        String sql = "select id, role_name, note from t_role where id = " + id;
        Role role = jdbcTemplate.queryForObject(sql, new RowMapper<Role>() {
            public Role mapRow(ResultSet rs, int i) throws SQLException {
                Role result = new  Role();
                result.setId(rs.getLong("id"));
                result.setRoleName(rs.getString("role_name"));
                result.setNote(rs.getString("note"));
                return result;
            }
        });
        System.out.println("{id: " + role.getId() + ", role_name: "
                + role.getRoleName() + ", note: " + role.getNote() + "}");
    }
}

查询成功,其中代码里使用了匿名类, Java 8 也可以使用 Lambda 表达式。

增、删、改、查都可以使用JdbcTemplate。

public class JdbcOperation {
    public int insertRole(JdbcTemplate jdbcTemplate) {
        String roleName = "role_name_1";
        String note = "note_1";
        String sql = "insert into t_role(role_name, note) values(?, ?)";
        return jdbcTemplate.update(sql, roleName, note);
    }

    public int deleteRole(JdbcTemplate jdbcTemplate, Long id) {
        String sql = "delete from t_role where id=?";
        return jdbcTemplate.update(sql, id);
    }

    public int updateRole(JdbcTemplate jdbcTemplate, Role role) {
        String sql = "update t_role set role_name=?, note=? where id=?";
        return jdbcTemplate.update(sql, role.getRoleName(), role.getNote(), role.getId());
    }

    public List<Role> findRole(JdbcTemplate jdbcTemplate, String roleName) {
        String sql = "select id, role_name, note from t_role where role_name like concat('%', ?, '%')";
        Object[] params = {roleName};
        List<Role> list = jdbcTemplate.query(sql, params, (ResultSet rs, int rowNum) -> {
            Role result = new Role();
            result.setId(rs.getLong("id"));
            result.setRoleName(rs.getString("role_name"));
            result.setNote(rs.getString("note"));
            return result;
        });
        return list;
    }
}

如果需要执行多条SQL语句,可以使用 execute 方法,它将允许传递 ConnectionCallback 或者 StatementCallback 等接口进行回调。

MyBatis-Spring 项目

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。

依旧使用前面的数据源,创建项目并添加 Maven 依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wyk</groupId>
    <artifactId>springmybatisdemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <spring.version>4.3.2.RELEASE</spring.version>
        <mybatis.version>3.4.0</mybatis.version>
    </properties>

    <dependencies>
        <!-- spring核心包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- mybatis核心包 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>

        <!-- mybatis/spring包 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.25</version>
        </dependency>
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>


    </dependencies>

    <!--将xml文件加入maven构建 -->
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
</project>

然后创建实体类 Role, 添加数据源配置,和前面一样,这里不再赘述。

配置 SqlSessionFactoryBean

在 MyBatis 中, SqlSessionFactory 是产生 SqlSession 的基础,配置它十分关键。在 MyBatis-Spring 项目中提供了 SqlSessionFactoryBean 去支持 SqlSessionFactory 的配置。

在 XML 配置文件中配置 SqlSessionFactoryBean。

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:sqlMapConfig.xml" />
</bean>

这里引入了 MyBatis 的配置文件 sqlMapConfig.xml, 内容如下。

<?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="cacheEnabled" value="true" />
        <!-- 允许JDBC支持生成的键,需要适当的驱动 -->
        <setting name="useGeneratedKeys" value="true" />
        <!-- 配置默认的执行器 -->
        <setting name="defaultExecutorType" value="REUSE" />
        <!-- 全局启用延迟加载 -->
        <setting name="lazyLoadingEnabled" value="true" />
        <!-- 设置超时时间 -->
        <setting name="defaultStatementTimeout" value="25000" />
        <!-- 日志 -->
        <setting name="logImpl" value="LOG4J" />
    </settings>
    <!-- 别名配置 -->
    <typeAliases>
        <typeAlias alias="role" type="com.wyk.springmybatisdemo.domain.Role" />
    </typeAliases>
    <!-- 指定映射器路径 -->
    <mappers>
        <mapper resource="com/wyk/springmybatisdemo/mapper/RoleMapper.xml" />
    </mappers>
</configuration>

接下来,编写 MyBatis 的映射接口 RoleMapper.java 和 映射文件 RoleMapper.xml 。

package com.wyk.springmybatisdemo.mapper;

import com.wyk.springmybatisdemo.domain.Role;
import org.apache.ibatis.annotations.Param;

public interface RoleMapper {
    public int insertRole(Role role);
    public Role getRole(@Param("id") Long id);
    public int  updateRole(Role role);
    public int deleteRole(@Param("id") Long id);
}
<?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.wyk.springmybatisdemo.mapper.RoleMapper">
    <insert id="insertRole" useGeneratedKeys="true" keyProperty="id">
        insert into t_role(role_name, note) values (#{roleName}, #{note})
    </insert>

    <delete id="deleteRole" parameterType="long">
        delete from t_role where id=#{id}
    </delete>

    <select id="getRole" parameterType="long" resultType="role">
        select id, role_name as roleName, note from t_role where id = #{id}
    </select>

    <update id="updateRole" parameterType="role">
        update t_role
        set role_name = #{roleName},
        note = #{note}
        where id = #{id}
    </update>
</mapper>

到这里就基本完成了 MyBatis 框架的代码。

SqlSessionTemplate 组件

SqlSessionTemplate 并不是一个必备组件,使用并不是很广泛,但它也有一些自己的优点。首先,它是线程安全的,其次它提供了一系列增删改查的功能。

首先对它进行配置,需要引用带参数的构造方法。

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg ref="sqlSessionFactory" />
</bean>

然后就可以使用了。

public class SqlSessionTemplateTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cfg.xml");
        SqlSessionTemplate sqlSessionTemplate = ctx.getBean(SqlSessionTemplate.class);
        Role role = new Role();
        role.setRoleName("gjj");
        role.setNote("sqlSessionTempalte_note");
        sqlSessionTemplate.insert("com.wyk.springmybatisdemo.mapper.RoleMapper.insertRole", role);
        Long id = role.getId();
        sqlSessionTemplate.selectOne("com.wyk.springmybatisdemo.mapper.RoleMapper.getRole", id);
        role.setNote("update_sqlSessionTemplate");
        sqlSessionTemplate.update("com.wyk.springmybatisdemo.mapper.RoleMapper.updateRole", role);
        sqlSessionTemplate.delete("com.wyk.springmybatisdemo.mapper.RoleMapper.deleteRole", id);
    }
}

可以看到,在 sqlSessionTemplate 中需要使用字符串表明运行哪个 SQL ,IDE 无法检查其代码逻辑,所以逐渐被人们抛弃。

需要注意的一点是,当同时配置了 SqlSessionFactory 和 sqlSessionTemplate 的时候, sqlSessionTemplate 优先级更高。

配置 MapperFactoryBean

MyBatis-Spring 还提供了 MapperFactoryBean 作为中介类,通过配置它来实现 Mapper 接口。

<bean id="roleMapper"  class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.wyk.springmybatisdemo.mapper.RoleMapper" />
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

这样,就可以通过下面的代码去获得映射器。

RoleMapper roleMapper = ctx.getBean(RoleMapper.class);

配置 MapperScannerConfigurer

在映射文件较多的时候,一个一个配置会导致配置泛滥,因此提供了类 MapperScannerConfigurer, 通过扫描的形式进行配置。

首先介绍下 MapperScannerConfigurer 的主要配置。

可以看到有2种配置方式,一种是添加注解,一种是添加公共扩展接口,但是总是扩展一个接口会显得奇怪,一般使用注解 @Repositiory 来标注对应的 Mapper。

修改 RoleMapper。

package com.wyk.springmybatisdemo.mapper;

import com.wyk.springmybatisdemo.domain.Role;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface RoleMapper {
    public int insertRole(Role role);
    public Role getRole(@Param("id") Long id);
    public int  updateRole(Role role);
    public int deleteRole(@Param("id") Long id);
}

在配置文件中添加配置。

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.wyk.springmybatisdemo.mapper" />
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    <property name="annotationClass" value="org.springframework.stereotype.Repository" />
</bean>

添加测试代码,查看测试结果。

package com.wyk.springmybatisdemo.mainApp;

import com.wyk.springmybatisdemo.domain.Role;
import com.wyk.springmybatisdemo.mapper.RoleMapper;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringMybatisTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cfg.xml");
        RoleMapper roleMapper = ctx.getBean(RoleMapper.class);
        Role role = new Role();
        role.setRoleName("gjj1");
        role.setNote("gjj_note");
        roleMapper.insertRole(role);
        Long id = role.getId();
        System.out.println(id);
        roleMapper.getRole(id);
        role.setNote("update_note");
        roleMapper.updateRole(role);
        roleMapper.deleteRole(id);
    }
}
上一篇 下一篇

猜你喜欢

热点阅读