JDBC进阶 & BeanUtils组件简介

预编译sql处理(防止sql注入)
  • Statement : 执行SQL命令
    • CallableStatement : 执行存储过程
    • PreparedStatement : 预编译SQL语句执行
  • 使用预编译SQL语句的命令对象的好处 : 避免频繁sql拼接 (可以使用占位符), 可以防止sql注入
    • 例如: 登陆模块输入用户名,密码的时候, 可以避免用户输入的恶意密码
public class App {
    // 连接参数
    //private String url = "jdbc:mysql://localhost:3306/jdbc_demo";
    private String url = "jdbc:mysql:///jdbc_demo";
    private String user = "root";
    private String password = "root";
    private Connection con;
    private Statement stmt;
    private PreparedStatement pstmt;
    private ResultSet rs;
    // 1. 没有使用防止sql注入的案例
    @Test
    public void testLogin() {
        // 1.0 模拟登陆的用户名,密码
        String userName = "tom";
        //String pwd = "8881";
        String pwd = " ' or 1=1 -- ";
        // SQL语句
        String sql = "select * from admin where userName='"+userName+"'  and pwd='"+pwd+"' ";
        System.out.println(sql);
        try {
            // 1.1 加载驱动,创建连接
            Class.forName("com.mysql.jdbc.Driver");     
            con = DriverManager.getConnection(url, user, password);
            // 1.2 创建stmt对象
            stmt = con.createStatement();
            // 1.3 执行查询
            rs = stmt.executeQuery(sql);
            // 业务判断
            if (rs.next()) {
                System.out.println("登陆成功, 编号:" + rs.getInt("id"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 1.4 关闭
            try {
                rs.close();
                stmt.close();
                con.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    // 2. 使用PreparedStatement, 防止sql注入
    @Test
    public void testLogin2() {
        
        // 1.0 模拟登陆的用户名,密码
        String userName = "tom";
        //String pwd = "8881";
        String pwd = " ' or 1=1 -- ";
        
        // SQL语句
        String sql = "select * from admin where userName=?  and pwd=? ";
        try {
            // 1.1 加载驱动,创建连接
            Class.forName("com.mysql.jdbc.Driver");     
            con = DriverManager.getConnection(url, user, password);
            // 1.2 创建pstmt对象
            pstmt = con.prepareStatement(sql);   // 对sql语句预编译
            // 设置占位符值
            pstmt.setString(1, userName);
            pstmt.setString(2, pwd);
            
            // 1.3 执行
            rs = pstmt.executeQuery();
            if (rs.next()) {
                System.out.println("登陆成功," + rs.getInt("id"));
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 1.4 关闭
            try {
                rs.close();
                pstmt.close();
                con.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }   
}
存储过程调用
  • 存储过程的定义 :
-- 存储过程
-- 定义分隔符
DELIMITER $$
CREATE PROCEDURE proc_login()
BEGIN
   SELECT * FROM admin;
END $$
-- 调用
CALL proc_login;
  • 存储过程使用示例 :
public class App_call {
    // 全局参数
    private Connection con;
    private Statement stmt;
    private PreparedStatement pstmt;
    private CallableStatement cstmt;  // 存储过程
    private ResultSet rs;
    // 程序中调用存储过程
    @Test
    public void testCall() throws Exception {
        try {
            //1 . 创建连接
            con = JdbcUtil.getConnection();
            //2.  创建执行存储过程的stmt对象
            CallableStatement cstmt = con.prepareCall("CALL proc_login");
            //3.  执行(存储过程)
            rs = cstmt.executeQuery();
            
            // 遍历结果,测试
            if (rs.next()) {
                String name = rs.getString("userName");
                String pwd = rs.getString("pwd");
                // 测试
                System.out.println(name + pwd);
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
批处理
  • 业务场景:当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。
  • 执行批处理SQL语句:
    1. executeBatch()方法:执行批处理命令
    2. clearBatch()方法:清除批处理命令
  • 实现批处理有两种方式,
    • 第一种方式:Statement.addBatch(sql) list
      • 优点:可以向数据库发送多条不同的SQL语句。
      • 缺点:SQL语句没有预编译; 当向数据库发送多条语句相同,但仅参数不同的SQL语句时,需重复写上很多条SQL语句。例如:
  Insert into user(name,password) values(‘aa’,’111’);
  Insert into user(name,password) values(‘bb’,’222’);
  Insert into user(name,password) values(‘cc’,’333’);
  Insert into user(name,password) values(‘dd’,’444’);
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtil.getConnection();
String sql1 = "insert into user(name,password,email,birthday) 
 values('kkk','123','abc@sina.com','1978-08-08')";
String sql2 = "update user set password='123456' where id=3";
st = conn.createStatement();
st.addBatch(sql1);  //把SQL语句加入到批命令中
st.addBatch(sql2);  //把SQL语句加入到批命令中
st.executeBatch();
} finally {
 JdbcUtil.free(conn, st, rs);
}
  • 第二种方式:采用Statement.addBatch(sql)方式实现批处理:
    • 优点:发送的是预编译后的SQL语句,执行效率高
    • 缺点:只能应用在SQL语句相同,但参数不同的批处理中。因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据
        conn = JdbcUtil.*getConnection*();
        String sql = "insert into user(name,password,email,birthday) values(?,?,?,?)";
        st = conn.prepareStatement(sql);
        for(int i=0;i<50000;i++){
            st.setString(1, "aaa" + i);
            st.setString(2, "123" + i);
            st.setString(3, "aaa" + i + "@sina.com");
            st.setDate(4,**new** Date(1980, 10, 10));
            st.addBatch();
            if (i%1000==0){
                st.executeBatch();
                st.clearBatch();
            }
        }
        st.executeBatch();
  public class App {
 // 测试批处理操作
 @Test
  public void testBatch() throws Exception {
  
  // 模拟数据
  List<Admin> list = new ArrayList<Admin>();
 for (int i=1; i<21; i++) {
   Admin admin = new Admin();
   admin.setUserName("Jack" + i);
   admin.setPwd("888" + i);
   list.add(admin);
  }
  
  // 保存
  AdminDao dao = new AdminDao();
  dao.save(list);
 }
}
// 封装所有的与数据库的操作
  public class AdminDao {
 
 // 全局参数
private Connection con;
private PreparedStatement pstmt;
private ResultSet rs;

 // 批量保存管理员
 public void save(List<Admin> list) {
  // SQL
  String sql = "INSERT INTO admin(userName,pwd) values(?,?)";
  try {
   
   // 获取连接
   con = JdbcUtil.getConnection();
   // 创建stmt 
   pstmt = con.prepareStatement(sql);     // 【预编译SQL语句】
   
   for (int i=0; i<list.size(); i++) {
    Admin admin = list.get(i);
    // 设置参数
    pstmt.setString(1, admin.getUserName());
    pstmt.setString(2, admin.getPwd());
    
    // 添加批处理
    pstmt.addBatch();      // 【不需要传入SQL】
    
    // 测试:每5条执行一次批处理
    if (i % 5 == 0) {
     // 批量执行 
     pstmt.executeBatch();
     // 清空批处理
     pstmt.clearBatch();
    }
   }
   
   // 批量执行 
   pstmt.executeBatch();
   // 清空批处理
   pstmt.clearBatch();
   
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   JdbcUtil.closeAll(con, pstmt, rs);
  }
 }
}
插入数据,获取自增长值
  • 需求 : 李俊杰 : 18, 张相 : 19
  • 如何设计数据库?
 编号   员工姓名    年龄    部门
 01     李俊杰     18    开发部
 02     张三       19    开发部
  • 思考 : 如何减少数据冗余 -> 设置外键约束(部门与员工, 一对多的关系)
 编号    员工姓名    年龄    部门
 01       李俊杰    18      1
 02       张三      19      1

 部门编号     部门名称    
     1         开发部            
  • 设计数据库:
 员工表 (外键表) 【员工表有一个外键字段,引用了部门表的主键】
 部门表(主键表)
  • 编码总体思路 : 保存员工及其对应的部门
    • 步骤:先保存部门; 再得到部门主键,再保存员工
    • 开发具体步骤:
      1. 设计javabean
      2. 设计dao
      3. 测试
部门
CREATE TABLE dept(
   deptId INT PRIMARY KEY AUTO_INCREMENT,
   deptName VARCHAR(20)
);
-- 员工
CREATE TABLE employee(
   empId INT PRIMARY KEY AUTO_INCREMENT,
   empName VARCHAR(20),
   dept_id  INT   --  外键字段   
);
-- 给员工表添加外键约束
ALTER TABLE employee ADD CONSTRAINT FK_employee_dept_deptId
    FOREIGN KEY(dept_id) REFERENCES dept(deptId) ;
public class EmpDao {
    private Connection con;
    private PreparedStatement pstmt;
    private ResultSet rs;
    // 保存员工,同时保存关联的部门
    public void save(Employee emp){
        // 保存部门
        String sql_dept = "insert into dept(deptName) values(?)";
        // 保存员工
        String sql_emp = "INSERT INTO employee (empName,dept_id) VALUES (?,?)";
        // 部门id
        int deptId = 0;
        try {
            // 连接
            con = JdbcUtil.getConnection();
            /*****保存部门,获取自增长*******/
            // 【一、需要指定返回自增长标记】
            pstmt = con.prepareStatement(sql_dept,Statement.RETURN_GENERATED_KEYS);
            // 设置参数
            pstmt.setString(1, emp.getDept().getDeptName());
            // 执行
            pstmt.executeUpdate();
            // 【二、获取上面保存的部门子增长的主键】
            rs =  pstmt.getGeneratedKeys();
            // 得到返回的自增长字段
            if (rs.next()) {
                deptId = rs.getInt(1);
            }
            /*****保存员工*********/
            pstmt = con.prepareStatement(sql_emp);
            // 设置参数
            pstmt.setString(1, emp.getEmpName());
            pstmt.setInt(2, deptId);
            pstmt.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtil.closeAll(con, pstmt, rs);
        }
    }
}
事务
  • 基本概念 : 事务使指一组最小逻辑操作单元,里面有多个操作组成。 组成事务的每一部分必须要同时提交成功,如果有一个操作失败,整个操作就回滚
  • 事务ACID特性 :
    • 原子性(Atomicity) : 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
    • 一致性(Consistency) : 事务必须使数据库从一个一致性状态变换到另外一个一致性状态
    • 隔离性(Isolation) : 事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
    • 持久性(Durability) : 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
  • 事务的特性:
    • 原子性,是一个最小逻辑操作单元
    • 一致性,事务过程中,数据处于一致状态
    • 持久性, 事务一旦提交成功,对数据的更改会反映到数据库中
    • 隔离性, 事务与事务之间是隔离的
  • 案例 :
    • 需求 : 张三给李四转账
    • 设计 : 账户表
    • 技术 :
      • Connection :
        • void setAutoCommit(boolean autoCommit) : 设置事务是否自动提交; 如果设置为false,表示手动提交事务
      • void commit() : 手动提交事务
      • void rollback() : 回滚 (出现异常时候,所有已经执行成功的代码需要回退到事务开始前的状态)
      • Savepoint setSavepoint(String name)
    • 代码示例 :
-- 账户表
CREATE TABLE account(
   id INT PRIMARY KEY AUTO_INCREMENT,
   accountName VARCHAR(20),
   money DOUBLE
);
-- 转账
UPDATE account SET money=money-1000 WHERE accountName='张三';
UPDATE account SET money=money+1000 WHERE accountName='李四';
public class AccountDao {
    // 全局参数
    private Connection con;
    private PreparedStatement pstmt;
    // 1. 转账,没有使用事务
    public void trans1() {
        String sql_zs = "UPDATE account SET money=money-1000 WHERE accountName='张三';";
        String sql_ls = "UPDATE account SET money=money+1000 WHERE accountName='李四';";
        try {
            con = JdbcUtil.getConnection(); // 默认开启的隐士事务
            con.setAutoCommit(true);
            /*** 第一次执行SQL ***/
            pstmt = con.prepareStatement(sql_zs);
            pstmt.executeUpdate();
            /*** 第二次执行SQL ***/
            pstmt = con.prepareStatement(sql_ls);
            pstmt.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtil.closeAll(con, pstmt, null);
        }

    }
    // 2. 转账,使用事务
    public void trans2() {
        String sql_zs = "UPDATE account SET money=money-1000 WHERE accountName='张三';";
        String sql_ls = "UPDATE1 account SET money=money+1000 WHERE accountName='李四';";

        try {
            con = JdbcUtil.getConnection(); // 默认开启的隐士事务
            // 一、设置事务为手动提交
            con.setAutoCommit(false);

            /*** 第一次执行SQL ***/
            pstmt = con.prepareStatement(sql_zs);
            pstmt.executeUpdate();

            /*** 第二次执行SQL ***/
            pstmt = con.prepareStatement(sql_ls);
            pstmt.executeUpdate();

        } catch (Exception e) {
            try {
                // 二、 出现异常,需要回滚事务
                con.rollback();
            } catch (SQLException e1) {
            }
            e.printStackTrace();
        } finally {
            try {
                // 三、所有的操作执行成功, 提交事务
                con.commit();
                JdbcUtil.closeAll(con, pstmt, null);
            } catch (SQLException e) {
            }
        }
    }
    // 3. 转账,使用事务, 回滚到指定的代码段
    public void trans() {
        // 定义个标记
        Savepoint sp = null;
        // 第一次转账
        String sql_zs1 = "UPDATE account SET money=money-1000 WHERE accountName='张三';";
        String sql_ls1 = "UPDATE account SET money=money+1000 WHERE accountName='李四';";
        // 第二次转账
        String sql_zs2 = "UPDATE account SET money=money-500 WHERE accountName='张三';";
        String sql_ls2 = "UPDATE1 account SET money=money+500 WHERE accountName='李四';";

        try {
            con = JdbcUtil.getConnection(); // 默认开启的隐士事务
            con.setAutoCommit(false);       // 设置事务手动提交

            /*** 第一次转账 ***/
            pstmt = con.prepareStatement(sql_zs1);
            pstmt.executeUpdate();
            pstmt = con.prepareStatement(sql_ls1);
            pstmt.executeUpdate();
            
            // 回滚到这个位置?
            sp = con.setSavepoint(); 
            
            /*** 第二次转账 ***/
            pstmt = con.prepareStatement(sql_zs2);
            pstmt.executeUpdate();
            pstmt = con.prepareStatement(sql_ls2);
            pstmt.executeUpdate();

        } catch (Exception e) {
            try {
                // 回滚 (回滚到指定的代码段)
                con.rollback(sp);
            } catch (SQLException e1) {
            }
            e.printStackTrace();
        } finally {
            try {
                // 提交
                con.commit();
            } catch (SQLException e) {
            }
            JdbcUtil.closeAll(con, pstmt, null);
        }
    }
}
Jdbc中大文本类型的处理
  • 在实际开发中,程序需要把大文本或二进制数据保存到数据库
    • Oracle中大文本数据类型 : Clob(长文本类型, MySQL中不支持,使用的是text), Blob(二进制类型)
    • MySQL数据库: Text(长文本类型), Blob(二进制类型)
  • 基本概念:大数据也称之为LOB(Large Objects),LOB又分为:clob和blob
    • clob用于存储大文本。
    • blob用于存储二进制数据,例如图像、声音、二进制文等。
  • 对MySQL而言只有blob,而没有clob,mysql存储大文本采用的是Text,Text和blob分别又分为:
    • TINYTEXT、TEXT、MEDIUMTEXT和LONGTEXT
    • TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB
public class App_text {

    // 全局参数
    private Connection con;
    private Statement stmt;
    private PreparedStatement pstmt;
    private ResultSet rs;

    @Test
    // 1. 保存大文本数据类型   ( 写longtext)
    public void testSaveText() {
        String sql = "insert into test(content) values(?)";
        try {
            // 连接
            con = JdbcUtil.getConnection();
            // pstmt 对象
            pstmt = con.prepareStatement(sql);
            // 设置参数
            // 先获取文件路径
            String path = App_text.class.getResource("tips.txt").getPath();
            FileReader reader = new FileReader(new File(path));
            pstmt.setCharacterStream(1, reader);
            
            // 执行sql
            pstmt.executeUpdate();
            
            // 关闭
            reader.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtil.closeAll(con, pstmt, null);
        }
    }
    
    @Test
    // 2. 读取大文本数据类型   ( 读longtext)
    public void testGetAsText() {
        String sql = "select * from  test;";
        try {
            // 连接
            con = JdbcUtil.getConnection();
            // pstmt 对象
            pstmt = con.prepareStatement(sql);
            // 读取
            rs = pstmt.executeQuery();
            if (rs.next()) {
                // 获取长文本数据, 方式1:
                //Reader r = rs.getCharacterStream("content");
                
                // 获取长文本数据, 方式2:
                System.out.print(rs.getString("content"));
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtil.closeAll(con, pstmt, null);
        }
    }
}
Jdbc综合练习
  • 需求分析:登陆、注册、注销;
    • 登陆成功 : 显示所有的员工
    • 设计
      • 数据库设计:
        • Admin, 存放所有的登陆用户
        • Employee, 存放所有的员工信息
      • 系统设计
    • 系统结构
      • 分层: 基于mvc模式的分层
      • 项目用到的公用组件、类 (了解)
    • 编码

BeanUtils组件简介

  • 程序中对javabean的操作很频繁, 所以apache提供了一套开源的api,方便对javabean的操作!即BeanUtils组件(作用是简化javabean的操作)
  • 用户可以从www.apache.org下载BeanUtils组件,然后再在项目中引入jar文件!

导入BenUtils组件

  • 引入commons-beanutils-1.8.3.jar核心包
  • 引入日志支持包: commons-logging-1.1.3.jar
  • 如果缺少日志jar文件,报错:
    java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
    at org.apache.commons.beanutils.ConvertUtilsBean.<init>(ConvertUtilsBean.java:157)
    at org.apache.commons.beanutils.BeanUtilsBean.<init>(BeanUtilsBean.java:117)
    at org.apache.commons.beanutils.BeanUtilsBean$1.initialValue(BeanUtilsBean.java:68)

实例, 基本用法

  • 方法1: 对象属性的拷贝
    BeanUtils.copyProperty(admin, "userName", "jack");
    BeanUtils.setProperty(admin, "age", 18);
  • 方法2: 对象的拷贝
    BeanUtils.copyProperties(newAdmin, admin);
  • 方法3: map数据拷贝到javabean中 (注意:map中的key要与javabean的属性名称一致)
    BeanUtils.populate(adminMap, map);
public  void  test1()  throws  Exception {
 
 // a. 基本操作
 Admin admin = **new** Admin();
//  admin.setUserName("Jack");
//  admin.setPwd("999");
 
 // b. BeanUtils组件实现对象属性的拷贝
 BeanUtils.copyProperty(admin, "userName", "jack");
 BeanUtils.setProperty(admin, "age", 18); // copy, set 都可以
 
 // 总结1: 对于基本数据类型,会自动进行类型转换!
 
 // c. 对象的拷贝
 Admin newAdmin = new  Admin();
 BeanUtils.copyProperties(newAdmin, admin);
 
 // d. map数据,拷贝到对象中
 Admin adminMap = new  Admin();
 Map<String,Object> map = new  HashMap<String,Object>();
 map.put("userName", "Jerry");
 map.put("age", 29);
 // 注意:map中的key要与javabean的属性名称一致
 BeanUtils.populate(adminMap, map);
 
 // 测试
 System.out.println(adminMap.getUserName());
 System.out.println(adminMap.getAge());
}

实例, 日期类型的拷贝

  • 需要注册日期类型转换器,2种方式参见下面代码
 //2. 自定义日期类型转换器
    @Test
    public void test2() throws Exception {
        // 模拟表单数据
        String name = "jack";
        String age = "20";
        String birth = "   ";

        // 对象
        Admin admin = new Admin();
        
        // 注册日期类型转换器:1, 自定义的方式
        ConvertUtils.register(new Converter() {
            // 转换的内部实现方法,需要重写
            @Override
            public Object convert(Class type, Object value) {
                
                // 判断
                if (type != Date.class) {
                    return null;
                }
                if (value == null || "".equals(value.toString().trim())) {
                    return null;
                }
                
                
                try {
                    // 字符串转换为日期
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                    return sdf.parse(value.toString());
                } catch (ParseException e) {
                    throw new RuntimeException(e);
                }
            }
        },Date.class);
        
        // 把表单提交的数据,封装到对象中
        BeanUtils.copyProperty(admin, "userName", name);
        BeanUtils.copyProperty(admin, "age", age);
        BeanUtils.copyProperty(admin, "birth", birth);
        
        //------ 测试------
        System.out.println(admin);
    }
    
    //2. 使用提供的日期类型转换器工具类
    @Test
    public void test3() throws Exception {
        // 模拟表单数据
        String name = "userName";
        String age = "20";
        String birth = null;
        
        // 对象
        Admin admin = new Admin();
        
        // 注册日期类型转换器:2: 使用组件提供的转换器工具类
        ConvertUtils.register(new DateLocaleConverter(), Date.class);
                
        // 把表单提交的数据,封装到对象中
        BeanUtils.copyProperty(admin, "userName", name);
        BeanUtils.copyProperty(admin, "age", age);
        BeanUtils.copyProperty(admin, "birth", birth);
        
        //------ 测试------
        System.out.println(admin);
    }
}

应用

public  class  WebUtils {

 @Deprecated
 public  static <T> T copyToBean_old(HttpServletRequest request, Class<T> clazz) {
  try {
   // 创建对象
   T t = clazz.newInstance();
   
   // 获取所有的表单元素的名称
   Enumeration<String> enums = request.getParameterNames();
   // 遍历
   while (enums.hasMoreElements()) {
    // 获取表单元素的名称:<input type="password" name="pwd"/>
    String name = enums.nextElement();  // pwd
    // 获取名称对应的值
    String value = request.getParameter(name);
    // 把指定属性名称对应的值进行拷贝
    BeanUtils.copyProperty(t, name, value);
   }
   
  return t;
  } catch (Exception e) {
   throw  new  RuntimeException(e);
  }
 }
 
 /**
  * 处理请求数据的封装
  */
  public  static  <T> T copyToBean(HttpServletRequest request, Class<T> clazz) {
  try {
   // (注册日期类型转换器)
   // 创建对象
   T t = clazz.newInstance();
   BeanUtils.populate(t, request.getParameterMap()); // 使用map
   return  t;
  } catch (Exception e) {
   throw  new  RuntimeException(e);
  }
 }
}

元数据

  • 在jdbc中获取数据库的定义,例如:数据库、表、列的定义信息, 就用到元数据
  • 在jdbc中可以使用: 数据库元数据、参数元数据、结果集元数据(元数据定义相关api, ..MetaData)
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

import org.junit.Test;

import anderson.sh.utils.JdbcUtil; // 自己的工具库

public class App {

    //1. 数据库元数据
    @Test
    public void testDB() throws Exception {
        // 获取连接
        Connection conn = JdbcUtil.getConnection();
        // 获取数据库元数据
        DatabaseMetaData metaData = conn.getMetaData(); // alt + shift + L  快速获取方法返回值
        
        System.out.println(metaData.getUserName());
        System.out.println(metaData.getURL());
        System.out.println(metaData.getDatabaseProductName());
    }
    
    //2. 参数元数据
    @Test
    public void testParams() throws Exception {
        // 获取连接
        Connection conn = JdbcUtil.getConnection();
        // SQL
        String sql = "select * from dept where deptid=? and deptName=?";
        // Object[] values = {"tom","888"};
        
        PreparedStatement pstmt = conn.prepareStatement(sql);
        // 参数元数据
        ParameterMetaData p_metaDate = pstmt.getParameterMetaData();
        // 获取参数的个数
        int count = p_metaDate.getParameterCount();
                
        // 测试
        System.out.println(count);
    }
    
    // 3. 结果集元数据
    @Test
    public void testRs() throws Exception {
        String sql = "select * from dept ";
        
        // 获取连接
        Connection conn = JdbcUtil.getConnection();
        PreparedStatement pstmt = conn.prepareStatement(sql);
        ResultSet rs = pstmt.executeQuery();
        // 得到结果集元数据(目标:通过结果集元数据,得到列的名称)
        ResultSetMetaData rs_metaData = rs.getMetaData();
        
        // 迭代每一行结果
        while (rs.next()) {
            // 1. 获取列的个数
            int count = rs_metaData.getColumnCount();
            // 2. 遍历,获取每一列的列的名称
            for (int i=0; i<count; i++) {
                // 得到列的名称
                String columnName = rs_metaData.getColumnName(i + 1);
                // 获取每一行的每一列的值
                Object columnValue = rs.getObject(columnName);
                // 测试
                System.out.print(columnName + "=" + columnValue + ",");
            }
            System.out.println();
        }   
    }   
}

Dao操作的抽取(BaseDAO)

  • Dao操作通用的步骤:
    1. 写SQL语句
    2. 获取连接
    3. 创建stmt
    4. 执行sql
    • 更新
    • 查询
    1. 关闭/异常
  • 通用的DAO(BaseDao)
    1. 更新 : public void update(String sql, Object[] paramValues);
    2. 查询 :
    • 传入的什么类型的对象,就封装为什么类型
    • 要求: 列的名称,要与指定类型的对象的属性名称一样)
      Public List<T> query (String sql , Object[] paramValues , Class<T> clazz);
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.beanutils.BeanUtils;
import anderson.sh.utils.JdbcUtil;

/**
 * 通用的dao,自己写的所有的dao都继承此类;
 * 此类定义了2个通用的方法:
 *  1. 更新
 *      2. 查询
 */
public class BaseDao {
    
    // 初始化参数
    private Connection con;
    private PreparedStatement pstmt;
    private ResultSet rs;

    /**
     * 更新的通用方法
     * @param sql   更新的sql语句(update/insert/delete)
     * @param paramsValue  sql语句中占位符对应的值(如果没有占位符,传入null)
     */
    public void update(String sql,Object[] paramsValue){
        
        try {
            // 获取连接
            con = JdbcUtil.getConnection();
            // 创建执行命令的stmt对象
            pstmt = con.prepareStatement(sql);
            // 参数元数据: 得到占位符参数的个数
            int count = pstmt.getParameterMetaData().getParameterCount();
            
            // 设置占位符参数的值
            if (paramsValue != null && paramsValue.length > 0) {
                // 循环给参数赋值
                for(int i=0;i<count;i++) {
                    pstmt.setObject(i+1, paramsValue[i]);
                }
            }
            // 执行更新
            pstmt.executeUpdate();
            
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            JdbcUtil.closeAll(con, pstmt, null);
        }
    }
    
    /**
     * 查询的通用方法
     * @param sql
     * @param paramsValue
     */
    public <T> List<T> query(String sql, Object[] paramsValue,Class<T> clazz){
        
        try {
            // 返回的集合
            List<T> list = new ArrayList<T>();
            // 对象
            T t = null;
            
            // 1. 获取连接
            con = JdbcUtil.getConnection();
            // 2. 创建stmt对象
            pstmt = con.prepareStatement(sql);
            // 3. 获取占位符参数的个数, 并设置每个参数的值
            //int count = pstmt.getParameterMetaData().getParameterCount();
            if (paramsValue != null && paramsValue.length > 0) {
                for (int i=0; i<paramsValue.length; i++) {
                    pstmt.setObject(i+1, paramsValue[i]);
                }
            }
            // 4. 执行查询
            rs = pstmt.executeQuery();
            // 5. 获取结果集元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            // ---> 获取列的个数
            int columnCount = rsmd.getColumnCount();
            
            // 6. 遍历rs
            while (rs.next()) {
                // 要封装的对象
                t = clazz.newInstance();
                
                // 7. 遍历每一行的每一列, 封装数据
                for (int i=0; i<columnCount; i++) {
                    // 获取每一列的列名称
                    String columnName = rsmd.getColumnName(i + 1);
                    // 获取每一列的列名称, 对应的值
                    Object value = rs.getObject(columnName);
                    // 封装: 设置到t对象的属性中  【BeanUtils组件】
                    BeanUtils.copyProperty(t, columnName, value);               
                }
                
                // 把封装完毕的对象,添加到list集合中
                list.add(t);
            }
            
            return list;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            JdbcUtil.closeAll(con, pstmt, rs);
        }
    }
}

import java.util.List;

public class AdminDao extends BaseDao {

    // 删除
    public void delete(int id) {
        String sql = "delete from admin where id=?";
        Object[] paramsValue = {id};
        super.update(sql, paramsValue);
    }

    // 插入
    public void save(Admin admin) {
        String sql = "insert into admin (userName,pwd) values (?,?)";
        Object[] paramsValue = {admin.getUserName(),admin.getPwd()};
        super.update(sql, paramsValue);
    }
    
    // 查询全部
    public List<Admin> getAll(){
        String sql = "select * from admin";
        List<Admin> list = super.query(sql, null, Admin.class);
        return list;
    }
    
    // 根据条件查询(主键)
    public Admin findById(int id){
        String sql = "select * from admin where id=?";
        List<Admin> list = super.query(sql, new Object[]{id}, Admin.class);
        return  (list!=null&&list.size()>0) ? list.get(0) : null;
    }   
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • JDBC简介 SUN公司为了简化、统一对数据库的操作,定义了一套Java操作数据库的规范,称之为JDBC。JDBC...
    奋斗的老王阅读 1,490评论 0 51
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,497评论 18 399
  • 一. Java基础部分.................................................
    wy_sure阅读 3,774评论 0 11
  • 一、事务 事务就是一个事情,组成这个事情可能有多个单元,要求这些单元,要么全都成功,要么全都不成功。在开发中,有事...
    野狗子嗷嗷嗷阅读 2,787评论 0 6
  • 你有经过美丽的橱窗看到漂亮的衣服,但就是穿着难看的尴尬经历吗? 你有着暴饮暴食然后全身不舒服一连难受好几天的悲伤往...
    大熊律师阅读 452评论 1 1