JDBCTemplate&连接池教程3

  • A+
所属分类:Java SQL MySql Oracle JavaWeb

学习目标

  1. 掌握JdbcTemplate实现增删改
  2. 掌握JdbcTemplate实现增查询
  3. 能够说出什么是数据库元数据
  4. 能够自定义数据库框架,实现增加、删除、更新方法
  5. 能够理解分层的作用
  6. 掌握使用三层架构实现用户注册案例

第1章 JdbcTemplate

1.1 JdbcTemplate概念

JDBC已经能够满足大部分用户最基本的需求,但是在使用JDBC时,必须自己来管理数据库资源如:获取PreparedStatement,设置SQL语句参数,关闭连接等步骤。JdbcTemplate就是Spring对JDBC的封装,目的是使JDBC更加易于使用。JdbcTemplate是Spring的一部分。 JdbcTemplate处理了资源的建立和释放。他帮助我们避免一些常见的错误,比如忘了总要关闭连接。他运行核心的JDBC工作流,如Statement的建立和执行,而我们只需要提供SQL语句和提取结果。 Spring源码地址:https://github.com/spring-projects/spring-framework 在JdbcTemplate中执行SQL语句的方法大致分为3类:

  1. execute:可以执行所有SQL语句,一般用于执行DDL语句。
  2. update:用于执行INSERTUPDATEDELETE等DML语句。
  3. queryXxx:用于DQL数据查询语句。

1.2 JdbcTemplate使用过程

1.2.1 Druid基于配置文件实现连接池

1.2.1.1 API介绍

org.springframework.jdbc.core.JdbcTemplate类方便执行SQL语句

  1. public JdbcTemplate(DataSource dataSource)
    创建JdbcTemplate对象,方便执行SQL语句
  2. public void execute(final String sql)
    execute可以执行所有SQL语句,因为没有返回值,一般用于执行DDL语句。

1.2.1.2 使用步骤

  1. 准备DruidDataSource连接池
  2. 导入依赖的jar包
    1. spring-beans-5.0.2.RELEASE.jar
    2. spring-core-5.0.2.RELEASE.jar
    3. spring-jdbc-5.0.2.RELEASE.jar
    4. spring-tx-5.0.2.RELEASE.jar
    5. com.springsource.org.apache.commons.logging-1.1.1.jar
  3. 创建JdbcTemplate对象,传入Druid连接池
  4. 调用executeupdatequeryXxx等方法

1.2.1.3 案例代码

public class Demo04 {
    public static void main(String[] args) {
        // 创建表的SQL语句
        String sql = "CREATE TABLE product("
                + "pid INT PRIMARY KEY AUTO_INCREMENT,"
                + "pname VARCHAR(20),"
                + "price DOUBLE"
                + ");";
                
        JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
        jdbcTemplate.execute(sql);
    }
}

1.2.1.4 案例效果

  • 代码效果

JDBCTemplate&连接池教程3

  • 执行SQL后创建数据库效果

JDBCTemplate&连接池教程3

1.3 JdbcTemplate实现增删改

1.3.1 API介绍

org.springframework.jdbc.core.JdbcTemplate类方便执行SQL语句

  1. public int update(final String sql)
    用于执行`INSERT`、`UPDATE`、`DELETE`等DML语句。

1.3.2 使用步骤

1.创建JdbcTemplate对象 2.编写SQL语句 3.使用JdbcTemplate对象的update方法进行增删改

1.3.3 案例代码

public class Demo05 {
    public static void main(String[] args) throws Exception {
//      test01();
//      test02();
//      test03();
    }
    
    // JDBCTemplate添加数据
    public static void test01() throws Exception {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
        
        String sql = "INSERT INTO product VALUES (NULL, ?, ?);";
        
        jdbcTemplate.update(sql, "iPhone3GS", 3333);
        jdbcTemplate.update(sql, "iPhone4", 5000);
        jdbcTemplate.update(sql, "iPhone4S", 5001);
        jdbcTemplate.update(sql, "iPhone5", 5555);
        jdbcTemplate.update(sql, "iPhone5C", 3888);
        jdbcTemplate.update(sql, "iPhone5S", 5666);
        jdbcTemplate.update(sql, "iPhone6", 6666);
        jdbcTemplate.update(sql, "iPhone6S", 7000);
        jdbcTemplate.update(sql, "iPhone6SP", 7777);
        jdbcTemplate.update(sql, "iPhoneX", 8888);
    }
    
    // JDBCTemplate修改数据
    public static void test02() throws Exception {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
        
        String sql = "UPDATE product SET pname=?, price=? WHERE pid=?;";
        
        int i = jdbcTemplate.update(sql, "XVIII", 18888, 10);
        System.out.println("影响的行数: " + i);
    }

    // JDBCTemplate删除数据
    public static void test03() throws Exception {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
        String sql = "DELETE FROM product WHERE pid=?;";
        int i = jdbcTemplate.update(sql, 7);
        System.out.println("影响的行数: " + i);
    }
}

1.3.4 案例效果

  • 增加数据效果

JDBCTemplate&连接池教程3

  • 修改数据效果

JDBCTemplate&连接池教程3

  • 删除数据效果

JDBCTemplate&连接池教程3

1.3.5 总结

JdbcTemplate的update方法用于执行DML语句。同时还可以在SQL语句中使用?占位,在update方法的Object... args可变参数中传入对应的参数。

1.4 JdbcTemplate实现查询

org.springframework.jdbc.core.JdbcTemplate类方便执行SQL语句

1.4.1 queryForObject返回一个指定类型

1.4.1.1 API介绍

public <T> T queryForObject(String sql, Class<T> requiredType, Object... args):
   传入参数, 执行查询语句,返回一个指定类型的数据。

1.4.1.2 使用步骤

  1. 创建JdbcTemplate对象
  2. 编写查询的SQL语句
  3. 使用JdbcTemplate对象的queryForObject方法,并传入需要返回的数据的类型
  4. 输出结果

1.4.1.3 案例代码

public static void test03() throws Exception {
   String sql = "SELECT pname FROM product WHERE price=7777;";
   JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
   String str = jdbcTemplate.queryForObject(sql, String.class);
   System.out.println(str);
}

1.4.1.4 案例效果

JDBCTemplate&连接池教程3

1.4.2 queryForMap返回一个Map集合对象

1.4.2.1 API介绍

  1. public Map<String, Object> queryForMap(String sql, Object... args)
    传入参数,执行查询语句,将一条记录放到一个Map中。

1.4.2.2 使用步骤

  1. 创建JdbcTemplate对象
  2. 编写查询的SQL语句
  3. 使用JdbcTemplate对象的queryForMap方法
  4. 处理结果

1.4.2.3 案例代码

public static void test04() throws Exception {
   String sql = "SELECT * FROM product WHERE pid=?;";
   JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
   Map<String, Object> map = jdbcTemplate.queryForMap(sql, 6);
   System.out.println(map);
}

1.4.2.4 案例效果

JDBCTemplate&连接池教程3

1.4.3 queryForList返回一个List集合对象,集合对象存储Map类型数据

1.4.3.1 API介绍

  1. public List<Map<String, Object>> queryForList(String sql, Object... args)
    传入参数,执行查询语句,返回一个List集合,List中存放的是Map类型的数据。

1.4.3.2 使用步骤

  1. 创建JdbcTemplate对象
  2. 编写查询的SQL语句
  3. 使用JdbcTemplate对象的queryForList方法
  4. 处理结果

1.4.3.3 案例代码

public static void test05() throws Exception {
   String sql = "SELECT * FROM product WHERE pid<?;";
   JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
   List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, 8);
   for (Map<String, Object> map : list) {
      System.out.println(map);
   }
}

1.4.3.4 案例效果

JDBCTemplate&连接池教程3

1.4.4 query使用RowMapper做映射返回对象

1.4.4.1 API介绍

  1. public <T> List<T> query(String sql, RowMapper<T> rowMapper)
    执行查询语句,返回一个List集合,List中存放的是RowMapper指定类型的数据。

1.4.4.2 使用步骤

  1. 定义Product类
  2. 创建JdbcTemplate对象
  3. 编写查询的SQL语句
  4. 使用JdbcTemplate对象的query方法,并传入RowMapper匿名内部类
  5. 在匿名内部类中将结果集中的一行记录转成一个Product对象

1.4.4.3 案例代码

// query使用rowMap做映射返回一个对象
public static void test06() throws Exception {
    JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());

   // 查询数据的SQL语句
   String sql = "SELECT * FROM product;";

   List<Product> query = jdbcTemplate.query(sql, new RowMapper<Product>() {
      @Override
      public Product mapRow(ResultSet arg0, int arg1) throws SQLException {
         Product p = new Product();
         p.setPid(arg0.getInt("pid"));
         p.setPname(arg0.getString("pname"));
         p.setPrice(arg0.getDouble("price"));
         return p;
      }
   });

   for (Product product : query) {
      System.out.println(product);
   }
}

1.4.4.4 案例效果

JDBCTemplate&连接池教程3

1.4.5 query使用BeanPropertyRowMapper做映射返回对象

1.4.5.1 API介绍

  1. public <T> List<T> query(String sql, RowMapper<T> rowMapper)
    执行查询语句,返回一个List集合,List中存放的是RowMapper指定类型的数据。
  2. public class BeanPropertyRowMapper<T> implements RowMapper<T>
    BeanPropertyRowMapper类实现了RowMapper接口

1.4.5.2 使用步骤

  1. 定义Product类
  2. 创建JdbcTemplate对象
  3. 编写查询的SQL语句
  4. 使用JdbcTemplate对象的query方法,并传入BeanPropertyRowMapper对象

1.4.5.3 案例代码

    // query使用BeanPropertyRowMapper做映射返回对象
    public static void test07() throws Exception {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());

        // 查询数据的SQL语句
        String sql = "SELECT * FROM product;";
        List<Product> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Product.class));

        for (Product product : list) {
            System.out.println(product);
        }
    }

1.4.4.4 案例效果

JDBCTemplate&连接池教程3

1.4.6 总结

JDBCTemplate的query方法用于执行SQL语句,简化JDBC的代码。同时还可以在SQL语句中使用占位,在query方法的Object... args可变参数中传入对应的参数。

第2章 数据库元数据

展开

在刚刚使用的jdbcTemplate中,提供了update和query的系列方法,就可以完成之前繁琐的jdbc操作,update方法操作的时候怎么获取到sql语句中的问号个数,以及如何设置进去参数呢?这里都需要使用到数据库的元数据.接下来我们来学习下.

2.1 元数据的基本概述

元数据:数据库、表、列的定义信息。

JDBCTemplate&连接池教程3

2.2 ParameterMetaData

ParameterMetaData可用于获取有关PreparedStatement对象中每个参数标记的类型和属性。

select * from user where name=? and password=?
// ParameterMetaData可以用来获取?的个数和类型

2.2.1 如何获取ParameterMetaData

通过PreparedStatementgetParameterMetaData()方法来获取到ParameterMetaData对象

2.2.2 API介绍

  1. int getParameterCount() 获取PreparedStatement的SQL语句参数?的个数
  2. int getParameterType(int param) 获取指定参数的SQL类型。  

2.2.3 使用步骤

  1. 获取ParameterMetaData对象
  2. 使用对象调用方法

2.2.4 注意事项

  1. 不是所有的数据库驱动都能后去到参数类型(MySQL会出异常)

2.2.5 案例代码

public class Demo01 {
    public static void main(String[] args) throws Exception {
        Connection conn = DataSourceUtils.getConnection();
        String sql = "INSERT INTO  student (name, age, score) VALUES (?, ?, ?)";
        PreparedStatement stmt = conn.prepareStatement(sql);
        ParameterMetaData md = stmt.getParameterMetaData();
        System.out.println("参数个数: " + md.getParameterCount());

        // Parameter metadata not available for the given statement
        // MySQL不支持获取参数类型
        // System.out.println("参数类型: " + md.getParameterType(1));
    }
}

2.3 ResultSetMetaData

ResultSetMetaData可用于获取有关ResultSet对象中列的类型和属性的信息。

JDBCTemplate&连接池教程3

2.3.1 如何获取ResultSetMetaData

JDBCTemplate&连接池教程3

通过ResultSetgetMetaData()方法来获取到ResultSetMetaData对象

2.3.2 API介绍

  1. int getColumnCount() 返回此 ResultSet对象中的列数
  2. String getColumnName(int column) 获取指定列的名称
  3. String getColumnTypeName(int column) 获取指定列的数据库特定类型名称

2.3.3 使用步骤

  1. 获取ResultSetMetaData对象
  2. 使用对象调用方法

2.3.4 案例代码

// ResultSetMetaData
public static void test02() throws SQLException {
    Connection conn = DataSourceUtils.getConnection();
    String sql = "SELECT * FROM student WHERE id=1";
    PreparedStatement stmt = conn.prepareStatement(sql);
    ResultSet rs = stmt.executeQuery();
    // 获取结果集元数据
    ResultSetMetaData md = rs.getMetaData();
    int num = md.getColumnCount();
    System.out.println("列数:" + num);
    for (int i = 0; i < num; i++) {
        System.out.println("列名:" + md.getColumnName(i + 1)); // 获取列名
        System.out.println("列类型:" + md.getColumnTypeName(i + 1)); // 获取类的类型
        System.out.println("-----------");
    }
}

2.3.5 案例效果

JDBCTemplate&连接池教程3

2.4 案例自定义数据库框架,实现增加、删除、更新方法

目前使用连接池工具类操作数据库的代码

// 增加数据
public static void testInsert() throws SQLException {
    // 获取连接
    Connection conn = DataSourceUtils.getConnection();

    String sql = "INSERT INTO student (name, age, score) VALUES (?, ?, ?);";
    PreparedStatement pstmt = conn.prepareStatement(sql);

    // 设置参数
    pstmt.setString(1, "刘德华");
    pstmt.setInt(2, 57);
    pstmt.setInt(3, 99);
    int i = pstmt.executeUpdate();
    System.out.println("影响的行数:" + i);

    // 关闭连接
    DataSourceUtils.close(conn, pstmt);
}

// 修改数据
public static void testUpdate() throws SQLException {
    Connection conn = DataSourceUtils.getConnection();

    String sql = "UPDATE student SET name=? WHERE id=?;";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, "张学友");
    pstmt.setInt(2, 2);
    int i = pstmt.executeUpdate();
    System.out.println("影响的行数:" + i);

    DataSourceUtils.close(conn, pstmt);
}

// 删除数据
public static void testDelete() throws SQLException {
    Connection conn = DataSourceUtils.getConnection();

    String sql = "DELETE FROM student WHERE id=?;";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    pstmt.setInt(1, 3);
    int i = pstmt.executeUpdate();
    System.out.println("影响的行数:" + i);

    DataSourceUtils.close(conn, pstmt);
}

JDBCTemplate&连接池教程3

存在的问题:我们发现,增删改都需要获取连接,关闭连接,只是SQL语句不同。我们可以使用元数据来自动给SQL设置参数,合并成一个方法。

DataSourceUtils中增加一个方法update

/*
    String sql: sql语句
    Object[] params: 参数数组
 */
public static int update(String sql, Object[] params) {
    Connection conn = null;
    PreparedStatement st = null;

    try {
        conn = getConnection();
        st = conn.prepareStatement(sql);
        // 获取到参数个数
        ParameterMetaData md = st.getParameterMetaData();
        int num = md.getParameterCount(); // 参数的个数
        for (int i = 0; i < num; i++) {
            // 将参数赋值给对应的?号
            st.setObject(i + 1, params[i]);
        }
        return st.executeUpdate();

    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(conn, st);
    }
    return 0;
}

增删改都调用update方法,只需要编写SQL语句和设置参数即可,可以减少代码。

// 增加数据
public static void testInsert2() throws SQLException {
    String sql = "INSERT INTO student (name, age, score) VALUES (?, ?, ?);";
    Object[] params = {"郭富城", 55, 88};
    int i = DataSourceUtils.update(sql, params);
    System.out.println("影响的行数:" + i);
}

// 修改数据
public static void testUpdate2() throws SQLException {
    String sql = "UPDATE student SET name=? WHERE id=?;";
    Object[] params = {"黎明", 1};
    int i = DataSourceUtils.update(sql, params);
    System.out.println("影响的行数:" + i);
}

// 删除数据
public static void testDelete2() throws SQLException {
    String sql = "DELETE FROM student WHERE id=?;";
    Object[] params = {7};
    int i = DataSourceUtils.update(sql, params);
    System.out.println("影响的行数:" + i);
}

第3章 三层架构

展开

3.1 分层的作用

我们之前的登录案例是将用户输入,数据库的操作,逻辑处理放在了同一个方法中,这样虽然非常直观,但是等项目做大的时候非常不好维护代码,也不好增加功能。

JDBCTemplate&连接池教程3

分层介绍: 公司中分层:

JDBCTemplate&连接池教程3

软件中分层:按照不同功能分为不同层,通常分为三层:表示层,业务层,数据访问层。

JDBCTemplate&连接池教程3

分层的作用:

  1. 解耦:降低层与层之间的耦合性。
  2. 可维护性:提高软件的可维护性,对现有的功能进行修改和更新时不会影响原有的功能。
  3. 可扩展性:提升软件的可扩展性,添加新的功能的时候不会影响到现有的功能。
  4. 可重用性:不同层之间进行功能调用时,相同的功能可以重复使用。

分层的方法:不同层次使用不同的包。

JDBCTemplate&连接池教程3

开发步骤: 从下向上开发:dao数据访问层 -> service业务逻辑层 -> view表示层

3.2 使用三层架构实现用户注册案例

3.2.1 案例需求

使用分层实现用户注册案例

3.2.2 案例效果

JDBCTemplate&连接池教程3

3.2.3 案例分析

  1. 使用数据库保存用户的账号和密码
  2. 定义User类
  3. 编写DAO层,增加saveUserfindUser方法
  4. 编写业务层增加registerisExist方法
  5. 编写View层增加register方法

3.2.4 实现步骤

  • 使用数据库保存用户的账号和密码,创建user表保存账号密码
CREATE TABLE `user` (
    uid INT PRIMARY KEY AUTO_INCREMENT,
    uname VARCHAR(20),
    upasswd VARCHAR(20)
);

INSERT INTO `user` (uname, upasswd) VALUES ('abc', '123');
INSERT INTO `user` (uname, upasswd) VALUES ('efg', '123');
INSERT INTO `user` (uname, upasswd) VALUES ('admin', '123');
  • 定义User类
/**
 * 用户类
 *
 */
public class User {
    private int uid;
    private String uname;
    private String upasswd;

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    public String getUname() {
        return uname;
    }

    public void setUname(String uname) {
        this.uname = uname;
    }

    public String getUpasswd() {
        return upasswd;
    }

    public void setUpasswd(String upasswd) {
        this.upasswd = upasswd;
    }

    public User(int uid, String uname, String upasswd) {
        super();
        this.uid = uid;
        this.uname = uname;
        this.upasswd = upasswd;
    }

    public User(String uname, String upasswd) {
        super();
        this.uname = uname;
        this.upasswd = upasswd;
    }

    public User() {
        super();
    }

    @Override
    public String toString() {
        return "User [uid=" + uid + ", uname=" + uname + ", upasswd=" + upasswd + "]";
    }
}
  • 编写DAO层,增加saveUserfindUser方法
public class UserDao {

    JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceUtils.getDataSource());

    // 保存用户到数据库
    public int saveUser(User user) {
        try {
            return jdbcTemplate.update("INSERT INTO user (uname, upasswd) values (?, ?);", user.getUname(), user.getUpasswd());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    // 从数据库查询是否有相同的记录
    public User findUser(String uname) {
        try {
            String sql = "SELECT * FROM user WHERE uname = ?;";
            // 注意:queryForObject如果没有找到数据,不是返回null,而是抛出异常,所以捕获异常,返回null
            return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), uname);
        } catch (Exception e) {
            return null;
        }
    }
}
  • 编写业务层增加registerisExist方法
/**
 * 业务层
 *
 */
public class UserService {

    private UserDao userDao = new UserDao();

    /**
     * 注册
     */
    public boolean register(User user) {
        // 保存用户信息到数据库
        return userDao.saveUser(user) > 0;
    }

    /**
     * 判断用户是否存在
     */
    public boolean isExist(String name) {
        return userDao.findUser(name) != null;
    }
}
  • 编写View层增加register方法
/*
    view表示层
 */
public class UserSystem {
    private static UserService userService = new UserService();
    private static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {
        System.out.println("-- 欢迎使用 --");
        while (true) {
            // 循环注册,知道注册成功才结束
            if (register()) {
                break;
            }
        }
    }

    /**
     * 注册的方法
     */
    private static boolean register() {
        System.out.println("请输入注册信息:");
        System.out.println("用户名:");
        String name = scanner.next();
        if (userService.isExist(name)) {
            System.out.println("用户名已经存在");
            return false;
        }
        System.out.println("密码:");
        String password = scanner.next();
        //封装数据
        User user = new User(name, password);
        if (userService.register(user)) {
            System.out.println("注册成功");
            return true;
        } else {
            System.out.println("注册失败");
            return false;
        }
    }
}

3.2.5 总结

当我们使用分层后,修改代码很方便,假如SQL语句写的有问题,只需要修改DAO层。使用分层后,增加功能也很方便,比如增加登录功能,在视图层增加登录的界面,在业务层增加登录的逻辑,DAO层直接使用原有代码。

分层的作用:

  1. 解耦:降低层与层之间的耦合性。
  2. 可维护性:提高软件的可维护性,对现有的功能进行修改和更新时不会影响原有的功能。
  3. 可扩展性:提升软件的可扩展性,添加新的功能的时候不会影响到现有的功能。
  4. 可重用性:不同层之间进行功能调用时,相同的功能可以重复使用。

  • 资源分享QQ群
  • weinxin
  • 官方微信公众号
  • weinxin
沙海
网站https安全证书安装,伪静态配置
美女讲师教你学C语言
Java图书管理系统
TripodCloud:性价比最高的CN2 GIA服务器

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: