JDBC 概念

Java Database Connectivity Java 连接数据库技术 简称 JDBC

JDBC规范接口:java 提供规范(接口),规定数据库操作方法标准的类库存在于 java.sql, javax.sql包下

数据库厂商会根据 java 的规范完成具体驱动的 jar 包

jdbc技术是一种典型的面向接口编程

JDBC 使用步骤

实现 Driver接口

下载 mysql 的驱动 下载链接

复制到 idea 项目下 ,新建 lib 文件夹 将 jar 添加到库

或者创建 maven项目直接到 mysql 的驱动文件

<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.33</version>
</dependency>

java实现创建连接

数据库创建对应的库表,这里是查询 myjdbc 的 t_user 表

public static void main(String[] args) throws SQLException {
  // 1注册驱动
  // mysql8 Driver -> com.mysql.cj.jdbc.Driver
  // 方法一
  DriverManager.registerDriver(new Driver());
  // 方法二 
  Class.forName("com.mysql.cj.jdbc.Driver");

  // 2获取连接
  // 数据库链接,用户名,密码
  String url = "jdbc:mysql://localhost:3306/myjdbc";
  String user = "root";
  String password = "admin123";
  Connection connection = DriverManager.getConnection(url, user, password);

  // 3创建statement
  Statement statement = connection.createStatement();

  // 4发送sql
  String sql = "select * from t_user ;";
  ResultSet resultSet = statement.executeQuery(sql);

  //5 解析查询结果
  while (resultSet.next()){
    int id = resultSet.getInt("id");
    String username = resultSet.getString("username");
    String email = resultSet.getString("email");
    System.out.println("id: "+ id +" username: "+username+" email: "+email);
  }

  // 6关闭资源 先开后关
  resultSet.close();
  statement.close();
  connection.close();
}

总结

注册方法:推荐使用第二种提供反射实现的 ,会加载 Driver 类只会注册一次驱动,而第二种方法 Driver类里也使用了 registerDriver的方法会注册两次\

数据库链接写法: 驱动程序://ip地:端口/数据库名称?参数(key=value)

  • 例如:jdbc:mysql://localhost:3306/myjdbc
  • 当前电脑的省略写法! 注意:本机和端口3306:jdbc:mysql:///myjdbc

DriverManager.getConnection 三个重载方法:

  • getConnection(String info):数据库url 携带账号密码例如 jdbc:mysql:///myjdbc?user=root&password=admin123
  • getConnection(String url, Properties properties):url 数据库地址;properties 参数封装容器至少要包含 user / password key!存储连接账号信息
  • getConnection(String url, String user, String password):url 数据库地址,user 用户名,password 密码

发送查询:

  • ResultSet 结果集对象 = executeQuery(DQL语句)
  • int 响应行数 = executeUpdate(非DQL语句)

ResultSet:结果集

  • 其中有方法 boolean = next() 来判断是非有下一行数据
  • 获取当前行列的数据 get类型(int columnIndex | String columnLabel) ,columnIndex 列的角标 从1开始 columnLabel 列名如果有别名则为别名

优化Statement

使用 Statement 可能有注入攻击问题,可使用预编译Statement来解决:preparedStatement

使用preparedStatement:

需要传入SQL语句结构

要的是SQL语句结构,动态值的部分使用 ? , 占位符!

只能 ? 不能加 '?' ? 只能替代值,不能替代关键字和容器名

// 若要传参查询 第3步改
String sql = "select * from t_user where username = ? and password = ? ;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 占位符赋值 从左到右,从1开始
preparedStatement.setObject(1,username);
preparedStatement.setObject(2,password);

// 4 发送sql
ResultSet resultSet = preparedStatement.executeQuery();

ResultSetMetaData 获取列信息对象

ResultSetMetaData 元数据**,是数据库列对象,**以列为单位封装为对象

常用方法

ResultSetMetaData 常用方法:
  1).  metaData.getColumnName(i)         获取该列的原始名字
  2).  metaData.getColumnLabel(i)        获取该列的别名
  3).  metaData.getColumnClassName(i)    获取该列的(在java中的)数据类型
  4).  metaData.getColumnType(i)         获取该列的(在数据库中的)数据类型对应的序号
  5).  metaData.getColumnTypeName(i)     获取该列的(在数据库中的)数据类型
  6).  metaData.getScale(i)              获取该列中小数点右边的位数
  7).  metaData.getColumnDisplaySize(i)  获取该列的长度
  8).  metaData.isAutoIncrement(i)       判断该列的值是否自动递增
  9).  metaData.isNullable(i)            判断该列的值是否为null
  10).  metaData.getTableName(i)         获取表名
  11). metaData.getColumnCount					 获取列数

例如 查询的数据存到 List<Map>

  • map -> 对应一行数据

  • map key -> 数据库列名或者别名

  • map value -> 数据库列的值

// ...省略上面的方法 4 获取结果
//创建一个集合
List<Map> mapList = new ArrayList<>();
//获取列信息对象 结果数量
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();

while (resultSet.next()) {
  Map map = new HashMap();
  for (int i = 1; i <= columnCount; i++) {
    // getColumnLabel,getObject 第一个是 从 1 开始
    // metaData.getColumnLabel(i) 列名
    // resultSet.getObject(i) 对应的值
    map.put(metaData.getColumnLabel(i), resultSet.getObject(i));
  }
  mapList.add(map);
}
// ...关闭资源

批量插入

当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率

批量插入方法

  • addBatch(String):添加需要批量处理的SQL语句或是参数
  • executeBatch():执行批量处理语句
  • clearBatch(): 清空缓存的数据
// 省略注册驱动 获取连接
long start = System.currentTimeMillis();
String sql = "insert into t_user(name) values(?)";
PreparedStatement ps = connection.prepareStatement(sql);
for(int i = 1;i <= 20000;i++){
  ps.setString(1, "name_" + i);
  // 存 sql 
  ps.addBatch();
  // 可以分段
  //  if(i % 500 == 0){
  //			// 执行
  //			ps.executeBatch();
  //			// 清空
  //			ps.clearBatch();
  //		}
}
// 将上面存的一并发送
ps.executeBatch();
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));
// 省略关闭资源

JDBC事务处理

当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。

**关闭数据库连接,数据就会自动的提交。**如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下

开启 jdbc 的事务管理

  • 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
  • 所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
  • 在出现异常时,调用 rollback(); 方法回滚事务
  • 关闭资源时,setAutoCommit(true) 恢复默认自动提交
public void testJDBCTransaction() {
	Connection conn = null;
	try {
		// 1.获取数据库连接 JDBCUtils封装工具类
		conn = JDBCUtils.getConnection();
		// 2.开启事务
		conn.setAutoCommit(false);
		// 3.进行数据库操作
		String sql1 = "update user_table set balance = balance - 100 where user = ?";
		update(conn, sql1, "AA");

		String sql2 = "update user_table set balance = balance + 100 where user = ?";
		update(conn, sql2, "BB");
		// 4.若没有异常,则提交事务
		conn.commit();
	} catch (Exception e) {
		e.printStackTrace();
		// 5.若有异常,则回滚事务
		try {
			conn.rollback();
		} catch (SQLException e1) {
			e1.printStackTrace();
		}
    } finally {
        try {
			//6.恢复每次DML操作的自动提交功能
			conn.setAutoCommit(true);
		} catch (SQLException e) {
			e.printStackTrace();
		}
        //7.关闭连接
		JDBCUtils.closeResource(conn, null, null); 
    }  
}

//使用事务以后的通用的增删改操作
public void update(Connection conn ,String sql, Object... args) {
	PreparedStatement ps = null;
	try {
		// 1.获取PreparedStatement的实例 (或:预编译sql语句)
		ps = conn.prepareStatement(sql);
		// 2.填充占位符
		for (int i = 0; i < args.length; i++) {
			ps.setObject(i + 1, args[i]);
		}
		// 3.执行sql语句
		ps.execute();
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		// 4.关闭资源
		JDBCUtils.closeResource(null, ps);
	}
}

druid

Druid 是一个数据连接池

  • 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个

jar 包链接 druid 1.28 导入 idea 项目库依赖

使用步骤

创建配置文件

例如 jdbc.properties 名字自定义 后缀需要 properties

配置文件中各种参数名不能随意取,只有像文中这样的参数名才能被识别

#驱动位置
driverClassName = com.mysql.cj.jdbc.Driver
#通信地址
url=jdbc:mysql:///myjdb
#用户名
username=root
#密码
password=123456
#初始化连接数
initialSize=2
#最大连接数
maxActive=5
#最大等待时间(毫秒)
maxWait=5000

java 代码

public class DruidTest {
  public static void main(String[] args) throws Exception {
    //创建Properties对象
    Properties ps = new Properties();
    //创建输入流,获取配置文件中的数据
    InputStream is = DruidTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
    //使用Properties对象来读取配置文件
    ps.load(is);
    // 创建Druid数据库连接池 将文件对象传入
    DataSource dataSource = DruidDataSourceFactory.createDataSource(ps);
    //创建连接对象
    Connection connection = dataSource.getConnection();
    // ... 数据库操作
    //关闭连接
    connection.close();
  }
}

利用封装工具类

封装一个连接池对象

利用线程本地变量,存储连接信息! 确保一个线程的多个方法可以获取同一个 connection!

  • 优势: 事务操作的时候 service 和 dao 属于同一个线程,不同再传递参数了!
  • 大家都可以调用getConnection自动获取的是相同的连接池
public class JdbcUtils {
  //连接池对象
  private static DataSource dataSource = null; 
  // 线程本地变量
  private static ThreadLocal<Connection> tl = new ThreadLocal<>();
  static{
    //初始化连接池对象
    Properties properties = new Properties();
    InputStream ips = JdbcUtilsV2.class.getClassLoader().getResourceAsStream("jdbc.properties");
    try {
      properties.load(ips);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    try {
      dataSource = DruidDataSourceFactory.createDataSource(properties);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  /**
     * 对外提供连接的方法
     * @return
     */
  public static Connection getConnection() throws SQLException {
    //线程本地变量中是否存在
    Connection connection = tl.get();
    //第一次创建时没有
    if (connection == null) {
      // 线程本地变量没有,连接池获取
      connection = dataSource.getConnection();
      tl.set(connection);
    }
    return  connection;
  }

  public static void freeConnection() throws SQLException {
    // 获取线程本地变量
    Connection connection = tl.get();
    if (connection != null) {
      tl.remove(); //清空线程本地变量数据
      connection.setAutoCommit(true); //事务状态回顾 false
      connection.close(); //回收到连接池即可
    }
  }
}

封装dao数据库重复代码

封装简化非DQL语句和封装简化DQL语句

// 封装简化非DQL语句 参数 sql语句,?占位符的值 返回影响行数
public int executeUpdate(String sql,Object... params) throws SQLException {

  //获取连接
  Connection connection = JdbcUtilsV2.getConnection();
 // 创建 preparedStatement
  PreparedStatement preparedStatement = connection.prepareStatement(sql);
  //5.占位符赋值
  //可变参数可以当做数组使用
  for (int i = 1; i <= params.length; i++) {
    preparedStatement.setObject(i, params[i-1]);
  }
  //6.发送SQL语句
  //DML类型
  int rows = preparedStatement.executeUpdate();

  preparedStatement.close();
  //是否回收连接,需要考虑是不是事务!
  if (connection.getAutoCommit()) {
    //没有开启事务
    //没有开启事务 正常回收连接啦!
    JdbcUtilsV2.freeConnection();
  }
	 //如果开启事务了 不要管连接即可! 业务层处理
  return rows;
}
// 封装简化DQL语句
//参数: 与数据库对应的实体类,sql语句,?占位符的值
public <T> List<T> executeQuery(Class<T> clazz, String sql,Object... params) throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException {

  //获取连接
  Connection connection = JdbcUtilsV2.getConnection();
 // 创建 preparedStatement
  PreparedStatement preparedStatement = connection.prepareStatement(sql);
  //占位符赋值
  if (params != null && params.length != 0) {
    for (int i = 1; i <= params.length; i++) {
      preparedStatement.setObject(i,params[i-1]);
    }
  }
  //6.发送SQL语句
  ResultSet resultSet = preparedStatement.executeQuery();
  //7.结果集解析
  List<T> list = new ArrayList<>();

  //获取列的信息对象
  //metaData 装的当前结果集列的信息对象
  ResultSetMetaData metaData = resultSet.getMetaData();
  //获取数量用来水平遍历列!
  int columnCount = metaData.getColumnCount();

  while (resultSet.next()) {
    T t = clazz.newInstance(); //调用类的无参构造函数实例化对象!
    //自动遍历列 注意,要从1开始,并且小于等于总列数!
    for (int i = 1; i <= columnCount; i++) {
      //对象的属性值
      Object value = resultSet.getObject(i);
      //获取指定列下角标的列的名称! ResultSetMetaData
      String propertyName = metaData.getColumnLabel(i);
      //反射,给对象的属性值赋值
      Field field = clazz.getDeclaredField(propertyName);
      field.setAccessible(true); //属性可以设置,打破private的修饰限制
      /**
      * 参数1: 要要赋值的对象  如果属性是静态,第一个参数 可以为null!
      * 参数2: 具体的属性值
      */
      field.set(t,value);
    }
    //一行数据的所有列全部存到了map中!
    //将map存储到集合中即可
    list.add(t);
  }
  //关闭资源
  resultSet.close();
  preparedStatement.close();

  if (connection.getAutoCommit()){
    //没有事务 可以关闭
    JdbcUtilsV2.freeConnection();
  }
  return list;
}

注意:数据库数据要有对应 java实体类

例如 数据库表 t_user 里有字段idusernamepassword;则 java 要有对应实体类 User 成员变量 idusernamepaasword

练习增删改查

创建数据表

create table ssm.t_student
(
    id       int auto_increment
        primary key,
    username varchar(255) null,
    password varchar(255) null
);

创建对应实体类

public class Student {
    private int id;
    private String username;
    private String password;

    public Student(String username, String password) {
        this.username = username;
        this.password = password;
    }
		public Student(){}
    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

查询

// 查询所有
public List selectAll() throws Exception {
  String sql = "select * from t_student;";
  List<Student> students = BaseDao.executeQuery(Student.class, sql);
  return  students;
}
// 根据id查询
public List selectById(int id) throws Exception{
  String sql = "select * from t_student where id =?;";
  List<Student> students = BaseDao.executeQuery(Student.class, sql,id);
  return  students;
}

添加

public boolean addStu(Student student) throws SQLException {
  if(student==null){return false;}
  String username = student.getUsername();
  String password = student.getPassword();
  String sql = "insert into t_student(username,password) values(?,?) ";
  int res = BaseDao.executeUpdate(sql, username, password);
  return res > 0;
}

修改

public boolean updateById(int id,Student student) throws SQLException {
  if(student==null){return false;}
  String username = student.getUsername();
  String password = student.getPassword();
  String sql ="update t_student set username=?,password=? where id=?";
  int res = BaseDao.executeUpdate(sql, username, password, id);
  return res>0;
}

删除

public boolean removeById(int id) throws SQLException {
  String sql ="delete from t_student where id=?;";
  int res = BaseDao.executeUpdate(sql, id);
  return res>0;
}

测试

@Test
public void test() throws Exception {
  Student zs = new Student("zs", "123456");
  // 添加
  boolean res = addStu(zs);
  System.out.println(res);
  // 查询
  List list1 = selectAll();
  list1.forEach(System.out::println);
  List list2 = selectById(2);
  list2.forEach(System.out::println);
  // 修改
  Student zs1 = new Student("zs","654321");
  boolean res1 = updateById(1, zs1);
  System.out.println(res1);
  // 删除
  boolean res2 = removeById(1);
  System.out.println(res2);
}

参考资料

尚硅谷jdbc教学