SpringBoot整合JdbcTemplate

参考文章

主要内容

  • JdbcTemplate其实就是用来操作数据库的,是最基本的Spring JDBC模板,是对JDBC很轻的封装,具有较高的执行效率(基于jdbc的数据操作要比hibernate那些效率都高,尤其批量处理),一般用于系统数据库并发操作压力大的项目中。
  • Spring数据访问模板:在数据库操作过程中,有很多重复工作,如事务控制、资源管理和异常处理等,Spring的模板类封装了这些固定部分。
  • 主要包含以下四类方法:
    • execute():执行任何sql语句,一般用于执行ddl语句。
    • update()、batchUpdate():执行(批量执行新增、修改、删除语句。
    • query()、queryForXxx():执行查询语句。
    • call():执行存储过程、函数等语句;当然,前面的三种方法其sql语句都可以带函数。
  • 参考文章
  • 首先操作数据库需要添加以下依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
  • 在application.properties中配置数据源

    spring.datasource.url = jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf-8
    spring.datasource.username = root
    spring.datasource.password = root
    spring.datasource.driver-class-name = com.mysql.jdbc.Driver
    
  • spring-boot-starter-jdbc默认使用tomcat-jdbc数据源,推荐使用其他数据源尤其是阿里巴巴的数据池管理,请额外添加以下依赖

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.19</version>
    </dependency>
    
  • 连接池详情参见笔记:连接池
  • 然后在Application.java配置DataSource,这里介绍两种方式
  • 第一种:application.properties只配置jdbc驱动,数据池配置则全写在入口类

    @SpringBootApplication
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    
        //Environment这个类是用来加载application.properties文件中的属性的
        @Autowired
        private Environment env;
    
        //destroy-method="close"的作用是当数据库连接不使用的时候,就把该连接重新放到数据池中,方便下次使用调用.
        @Bean(destroyMethod =  "close")
        public DataSource dataSource() {
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setUrl(env.getProperty("spring.datasource.url")); //引用配置文件的变量
            dataSource.setUsername(env.getProperty("spring.datasource.username"));//用户名
            dataSource.setPassword(env.getProperty("spring.datasource.password"));//密码
            dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
            dataSource.setInitialSize(2);//初始化时建立物理连接的个数
            dataSource.setMaxActive(20);//最大连接池数量
            dataSource.setMinIdle(0);//最小连接池数量
            dataSource.setMaxWait(60000);//获取连接时最大等待时间,单位毫秒。
            dataSource.setValidationQuery("SELECT 1");//用来检测连接是否有效的sql
            dataSource.setTestOnBorrow(false);//申请连接时执行validationQuery检测连接是否有效
            dataSource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。
            dataSource.setPoolPreparedStatements(false);//是否缓存preparedStatement,也就是PSCache
            return dataSource;
        }
    }
    
  • 第二种:在application.properties中写全部配置,在入口类用@ConfigurationProperties直接把变量封装到类中

    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/test
    spring.datasource.username=root
    spring.datasource.password=123456
    
    #阿里巴巴连接池配置
    #最大连接池数量
    spring.datasource.max-active=20 
    #最小连接池数量
    spring.datasource.min-idle=8 
    #获取连接时最大等待时间
    spring.datasource.maxWait=60000
    #初始化时建立连接的个数
    spring.datasource.initialSize=2
    #申请连接时执行ValidationQuery检测连接是否有效,建议为false,因为做了这个配置会降低性能;若下面那行括号为null则这一行怎么写都不生效
    spring.datasource.testOnBorrow=false
    #用来检测连接是否有效的sql,要求是一个查询语句
    spring.datasource.validationQuery="SELECT 1"
    #建议配置为true,不影响性能且保证安全性;作用是在ValidationQuery上加个条件:当空闲时间大于timeBetweenEvictionRunsMillis时才执行连接检测
    spring.datasource.testWhileIdle=true
    #是否缓存preparedStatement
    spring.datasource.poolPreparedStatements=true
    
  • 入口类
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }

    @Bean(destroyMethod = "close") //destroyMethod = "close":当数据库连接不使用时就把该连接重新放到数据池中,方便下次调用
    @ConfigurationProperties(prefix="spring.datasource") //直接使用配置文件中前缀为这个的属性来set DataSource的属性
    public DataSource dataSource(){
        return new DruidDataSource();
    }
}
  • Springboot会自动选择我们自己配置的这个DataSource实例
  • 在dao层注入jdbcTemplate,它有很多操作数据库的方法
@Repository
public class LearnDaoImpl  implements LearnDao{
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int add(LearnResouce learnResouce) {
        return jdbcTemplate.update("insert into learn_resource(author, title,url) values(?, ?, ?)",learnResouce.getAuthor(),learnResouce.getTitle(),learnResouce.getUrl());
    }
}

代码实例:SpringMvc02

  • 应该是跑不起来,反正看一看理解原理就是了

Spring整合JdbcTemplate

  • 引入依赖

    <dependency> <!--使用JdbcTemplate,方便数据库操作-->
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.0.0.RELEASE</version>
    </dependency>
    
  • JdbcTemplateUtils:获取JdbcTemplate对象,单例模式

    /**
     * @Author haien
     * @Description 获取JdbcTemplate对象,单例模式
     * @Date 2019/2/24
     **/
    public class JdbcTemplateUtils {
        private static JdbcTemplate jdbcTemplate;
    
        /**
         * @Author haien
         * @Description 单例模式
         * @Date 2019/2/24
         * @Param []
         * @return org.springframework.jdbc.core.JdbcTemplate
         **/
        public static JdbcTemplate jdbcTemplate(){
            if(jdbcTemplate==null)
                jdbcTemplate=createJdbcTemplate();
    
            return jdbcTemplate;
        }
    
        /**
         * @Author haien
         * @Description 配置数据源
         * @Date 2019/2/25
         * @Param []
         * @return org.springframework.jdbc.core.JdbcTemplate
         **/
        private static JdbcTemplate createJdbcTemplate(){
            DruidDataSource ds=new DruidDataSource();
            ds.setDriverClassName("com.mysql.jdbc.Driver");
            ds.setUrl("jdbc:mysql://127.0.0.1:3306/shiro");
            ds.setUsername("root");
            ds.setPassword("123456");
    
            return new JdbcTemplate(ds);
        }
    }
    
  • UserDaoImpl:使用JdbcTemplate对象
public class UserDaoImpl implements UserDao {

    private JdbcTemplate jdbcTemplate = JdbcTemplateUtils.jdbcTemplate();

    @Override
    public User createUser(final User user) {
        final String sql = "insert into sys_users(username, password, salt, locked) " +
                "values(?,?,?,?)";

        GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection connection)
                    throws SQLException {
                //没有第二个参数应该也能获取到新生id
                PreparedStatement psst = connection.prepareStatement(sql, new String[] { "id" });
                psst.setString(1, user.getUsername());
                psst.setString(2, user.getPassword());
                psst.setString(3, user.getSalt());
                psst.setBoolean(4, user.getLocked());
                return psst;
            }
        }, keyHolder);

        user.setId(keyHolder.getKey().longValue());
        return user;
    }

    @Override
    public void updateUser(User user) {
        String sql = "update sys_users set username=?, password=?, salt=?, locked=? where id=?";
        jdbcTemplate.update(sql, user.getUsername(), user.getPassword(), user.getSalt(),
                user.getLocked(), user.getId());
    }

    @Override
    public void deleteUser(Long userId) {
        String sql = "delete from sys_users where id=?";
        jdbcTemplate.update(sql, userId);
    }

    @Override
    public void correlationRoles(Long userId, Long... roleIds) {
        if(roleIds == null || roleIds.length == 0) {
            return;
        }
        String sql = "insert into sys_users_roles(user_id, role_id) values(?,?)";
        for(Long roleId : roleIds) {
            if(!exists(userId, roleId)) {
                jdbcTemplate.update(sql, userId, roleId);
            }
        }
    }

    @Override
    public void uncorrelationRoles(Long userId, Long... roleIds) {
        if(roleIds == null || roleIds.length == 0) {
            return;
        }
        String sql = "delete from sys_users_roles where user_id=? and role_id=?";
        for(Long roleId : roleIds) {
            if(exists(userId, roleId)) {
                jdbcTemplate.update(sql, userId, roleId);
            }
        }
    }

    private boolean exists(Long userId, Long roleId) {
        String sql = "select count(1) from sys_users_roles where user_id=? and role_id=?";
        return jdbcTemplate.queryForObject(sql, Integer.class, userId, roleId) != 0;
    }


    @Override
    public User findOne(Long userId) {
        String sql = "select id, username, password, salt, locked from sys_users where id=?";
        List<User> userList = jdbcTemplate.query(sql,
                new BeanPropertyRowMapper(User.class), userId);
        if(userList.size() == 0) {
            return null;
        }
        return userList.get(0);
    }

    @Override
    public User findByUsername(String username) {
        String sql = "select id, username, password, salt, locked from sys_users where username=?";
        List<User> userList = jdbcTemplate.query(sql,
                new BeanPropertyRowMapper(User.class), username);
        if(userList.size() == 0) {
            return null;
        }
        return userList.get(0);
    }

    @Override
    public Set<String> findRoles(String username) {
        String sql = "select role from sys_users u, sys_roles r,sys_users_roles ur " +
                "where u.username=? and u.id=ur.user_id and r.id=ur.role_id";
        return new HashSet(jdbcTemplate.queryForList(sql, String.class, username));
    }

    @Override
    public Set<String> findPermissions(String username) {
        //TODO 此处可以优化,比如查询到role后,一起获取roleId,然后直接根据roleId获取即可
        String sql = "select permission from sys_users u, sys_roles r, sys_permissions p, " +
                "sys_users_roles ur, sys_roles_permissions rp " +
                "where u.username=? and u.id=ur.user_id and r.id=ur.role_id " +
                "and r.id=rp.role_id and p.id=rp.permission_id";
        return new HashSet(jdbcTemplate.queryForList(sql, String.class, username));
    }
}
  • jdbcTemplate.queryForInt() 和 jdbcTemplate.queryForLong():返回查询结果条数,根据预估数目大小选择int或long
String sql="SELECT count(*) FROM mcp_forum_post";
Integer todayCount=jdbcTemplate.queryForInt(sql);
//或
Long todayCount=jdbcTemplate.queryForLong(sql);
  • jdbcTemplate.queryForObject(String sql, Class requiredType, sql语句所需参数): 返回查询sql中指定字段,并用requiredType类型承接。要求结果集只有一条记录,否则抛异常。
String sql="select account from scpn_user where user_id="+userAccountId;
//sql指明返回account字段,故返回account字段
//account是String类,但即使account是int型,指定为String类也可以,不知是否指定为任何类都可以
String userAccount=(String)jdbcTemplate.queryForObject(sql, String.class);
  • 若sql语句查询所有字段,则无论requiredType是什么,都只返回查询到的记录总数,也就是0或1,常用来判断查询是否成功。
String sql="select * from scpn_user where user_id="+userAccountId;
String userAccount=(String)jdbcTemplate.queryForObject(sql, String.class); //userAccount=1
  • jdbcTemplate.queryForMap():查询一条记录,将各个字段及其值作为键值对封装进Map。
String userAccountSql="select account,create_time from scpn_user 
    where user_id="+userAccountId;
Map userAccountMap=(Map)jdbcTemplate.queryForMap(userAccountSql);

String userAccount= (String)userAccountMap.get("account");
String createTime= (String)userAccountMap.get("create_time").toString();
  • jdbcTemplate.queryForList():查询多条记录,一条一个Map,多个Map组成一个List
String all="SELECT * FROM mcp_forum_post";
List scpnPostList = jdbcTemplate.queryForList(all);
if (scpnPostList!=null) {
    for (int i = 0; i < scpnPostList.size(); i++) {
        Long userAccountId = (Long) scpnPostList.get(i).get("user_id");
        Long lastmodUser = (Long) scpnPostList.get(i).get("lastmod_user");
    }
}
//需定义一个匿名实现类
User user = jdbcTemplate.query(sql,new RowMapper<User>(){  
        @Override  
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {  
            User user = new User();  
            user.setId(rs.getInt("id"));  
            user.setUsername(rs.getString("username"));  
            user.setPassword(rs.getString("password"));  
            return user;  
        }  
    }); 

//或者放一个自定义实现类进去
user = jdbcTemplate.query(sql, new UserRowMapper());
//实现类
public class UserRowMapper implements RowMapper<User> {  
    @Override  
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {  
        User user = new User();  
        user.setId(rs.getInt("id"));  
        user.setUsername(rs.getString("username"));  
        user.setPassword(rs.getString("password"));  
        return user;  
    }  
}
  • 也可以使用Spring提供的RowMapper实现类BeanPropertyRowMapper:org.springframework.jdbc.core.BeanPropertyRowMapper,指定封装的类型即可。
//只能返回List
List<User> userList = jdbcTemplate.query(sql,
        new BeanPropertyRowMapper(User.class));
  • 实现原理:根据sql列名/别名找对应setter,映射规则就是大写变成”_小写”.
//比如role_ids字段,User类中只有setRoleIdsStr(),故要取别名
String sql = "select id, organization_id, username, password, salt, role_ids " +
        "as roleIdsStr, locked from sys_user where id=?";
List<User> userList = jdbcTemplate.query(sql,
        new BeanPropertyRowMapper(User.class), userId);
  • 代码实例:ideaProjects/shiroHelloWorld/utils/JdbctemplateUtils、dao/impl/UserDaoImpl;ideaProjects/shirochapter16/dao/impl/UserDaoImpl

JdbcDaoSupport

  • 使用了JdbcTemplate后,我们在书写dao层时还要每个dao都引入JdbcTemplate的实例,而继承JdbcDaoSupport后,这个类就会负责处理JdbcTemplate对象,而且会负责数据库的自动关闭。
  • 方法如下:
//设置JdbcTemplate
public final void setJdbcTemplate(JdbcTemplate jdbcTemplate)
//获取JdbcTemplate
public final JdbcTemplate getJdbcTemplate()
  • 操作JdbcTemplate:
getJdbcTemplate().update(sql);

示例

  • PermissionDaoImpl:dao实现层,继承JdbcDaoSupport
public class PermissionDaoImpl extends JdbcDaoSupport implements PermissionDao {
    @Override
    public void deletePermission(Long permissionId) {
        String sql = "delete from sys_roles_permissions where permission_id=?";
        getJdbcTemplate().update(sql, permissionId);
    }
}
  • spring-beans.xml:注册PermissionDaoImpl、注入DataSource以创建JdbcTemplate(不用显式创建JdbcTemplate了)

    <!-- 数据库连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/shiro"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    
    <!-- Base DAO: abstract="true"即该类不能被实例化,默认false;抽象bean可以不映射任何类 -->
    <bean id="baseDao" abstract="true">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- DAO:虽然没有BaseDao这个实际类,PermissionDaoImpl类也不是BaseDao的子类,但它是
    JdbcDaoSupport的子类,继承了dataSource属性,现在指明parent为baseDao,则注入了dataSource
    属性进来,JdbcDaoSupport会以此创建JdbcTemplate的,所以上面dao层getJdbcTemplate()得到的
    就是这个JdbcTemplate-->
    <bean id="permissionDao" 
        class="com.haien.shirochapter12.dao.impl.PermissionDaoImpl" parent="baseDao"/>
    <!--之所以定义抽象类是因为有好多个dao需要这个属性-->
    <bean id="roleDao" class="com.haien.shirochapter12.dao.impl.RoleDaoImpl" parent="baseDao"/>
    <bean id="userDao" class="com.haien.shirochapter12.dao.impl.UserDaoImpl" parent="baseDao"/>
    
  • 代码实例:ideaProjects/shirochapter12/dao/impl、spring-beans.xml

  • 参考文章