jdbc连接数据scanip_java数据库连接_jdbc

1. jdbc 概述

问题:实际开发中,不可能用工具或者命令行操作数据库,数据库表中的数据最终要使用Java程序来操作,那么Java中如何操作数据库中的数据呢?

答 : 在Java语言中,有一个专门连接数据库的规范(JDBC),专门负责连接数据库进行数据操作的规范

JDBC只是SUN编写的一堆接口(规范的体现),SUN公司自己并没有实现

问题 : 为什么SUN只定义一个JDBC规范,而不实现呢?

答 : 因为市面上的数据库很多,每个数据库内部接口不会向外暴露,而且即便是暴露让SUN去实现,市面上很多数据库全部要SUN来实现的话就不现实

实际中哪个数据库需要支持JAVA语言,就需要自己实现Java的JDBC规范,因为实现了JDBC很多接口,那么就会有很多实现类,而很多实现类在java中会使用一个专门的包封装起来,叫做jar包(在JDBC中叫做驱动包),各大数据库产商实现JDBC规范以后都会把他们jar包放在官网上以供开发者下载使用

1.1 JDBC

JDBC(Java DataBase Connectivity): 是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基于JDBC规范对应的API包

232ef1a7c198e877c21390e9891b854d.png

2. 入门案例

2.1 连接数据库

案例使用JDBC操作MySQL数据库

2.2 创建普通java项目

2.3 在项目下面新建一个lib目录

2cc426427bbbcdda38c1c3f9b141acf5.png

2.4 将MySQL驱动包拷贝到项目中并添加依赖

ada2a397d9e9b8e1303698b0d39e2105.png

fb703679b7365fb468b47d6214d05627.png

2.5 获取数据库连接对象

准备:

1.拷贝MySQL的驱动包到项目中去:mysql-connector-java-5.1.x-bin.jar

2.build path,告速项目去哪里去找字节码文件.

--------------------------------------------------------------------------------

操作JDBC的第一步,获取JDBC的连接对象.:Connection.

步骤:

1.加载注册驱动.

就是把驱动中的Driver字节码加载到JVM中.

Class.forName("com.mysql.jdbc.Driver");

为什么这句话就可以加载注册驱动?

第一步:把com.mysql.jdbc.Driver.class这份字节码加载到JVM中.

第二步:当一份字节码被加载进JVM,马上就会执行该字节码中的静态代码块.

第三步:该静态代码中,就在完成,先创建驱动对象,再注册.

2.通过DriverManager获取连接对象.

public static Connection getConnection(String url,String user,String password)

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbName","root","admin");

jdbc:mysql://localhost:3306/dbName

jdbc:mysql:// :连接MySQL数据库的协议,不同数据库协议不一样

localhost:3306 :数据库软件的主机和端口

dbName 具体要连接数据库

若数据库安装在本机,并且端口是默认的3306,则可以简写:

Connection conn= DriverManager.getConnection("jdbc:mysql:///dbName","root","admin");

验证已经获取连接:可以在MySQL控制台,使用命令:show processlist; 查看MySQL运行进程。

dfdb2c9927ebc6193b0d4e15cba516c8.png
public class GetConnectionDemo {
	public static void main(String[] args) throws Exception {
		
		//1.加载注册驱动 : 把当前类对应的字节码加载到JVM中
		Class.forName("com.mysql.jdbc.Driver");
		//2.获取数据库连接
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
		System.out.println(conn);
		
	}
}

3. 创建表-DDL操作

在其他操作之间先要把数据库表要创建出来

/*创建一张t_student表:
id:
name:
age:
*/
	/*
	 * 
	 * 创建表操作
	 * SQL : create table t_student (id int primary key auto_increment,name varchar(50),age int)
	 */
	public static void main(String[] args) throws Exception {
		String sql = "create table t_student (id int primary key auto_increment,name varchar(50),age int)";
		//贾琏欲执事
		//1,加载注册驱动
		Class.forName("com.mysql.jdbc.Driver");
		//2,获取数据库连接
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
		//3,创建语句对象(用于执行SQL语句的对象)
		Statement st = conn.createStatement();
		//4, 执行SQL语句
		//int rows = st.executeUpdate(String sql);执行DDL和DML语句,返回的是受影响的行数
		//ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象
		st.executeUpdate(sql);
		//5,释放资源(先开后关)
		st.close();
		conn.close();
	}

4. DML操作-表数据的增删改

//DML : 对表数据的增删改操作
public class DMLDemo {

	/*
	 * 向 t_student表中插入一条数据 
	 * sql : insert into t_student(name,age) values ('乔峰',30)
	 */
	@Test
	public void testInsert() throws Exception {
		String sql = "insert into t_student(name,age) values ('乔峰',30)";
		
		// 1.加载注册驱动
		Class.forName("com.mysql.jdbc.Driver");

		// 2.获取数据库连接对象
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
		// 3.创建语句对象
		Statement st = conn.createStatement();
		
		// 4.执行SQL语句
		// int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数
		// ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象
		int rows = st.executeUpdate(sql);
		System.out.println(rows);
		//5.释放资源(先开后关)
		st.close();
		conn.close();

	}
	/*
	 * 删除操作: 删除t_student表中的某一条数据
	 * SQL :delete from t_student where id = 2
	 */
	@Test
	public void testDelete() throws Exception {
		String sql = "delete from t_student where id = 2";
		
		// 1.加载注册驱动
		Class.forName("com.mysql.jdbc.Driver");

		// 2.获取数据库连接对象
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
		// 3.创建语句对象
		Statement st = conn.createStatement();
		
		// 4.执行SQL语句
		// int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数
		// ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象
		int rows = st.executeUpdate(sql);
		System.out.println(rows);
		//5.释放资源(先开后关)
		st.close();
		conn.close();
	}
	/*
	 * 修改操作 : 修改t_student表中的某一条数据
	 * SQL : update t_student set name = '虚竹',age = 50 where id = 3
	 */
	@Test
	public void testUpdate() throws Exception {
		String sql = "update t_student set name = '虚竹',age = 50 where id = 3";
		
		// 1.加载注册驱动
		Class.forName("com.mysql.jdbc.Driver");

		// 2.获取数据库连接对象
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
		// 3.创建语句对象
		Statement st = conn.createStatement();
		
		// 4.执行SQL语句
		// int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数
		// ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象
		int rows = st.executeUpdate(sql);
		System.out.println(rows);
		//5.释放资源(先开后关)
		st.close();
		conn.close();
	}
}

5. DQL操作-查询操作

5.1 查询操作的分析

aa83701f70fc22692171a0c26344fb3a.png

5.2 查询具体操作

结果集的列的位置

31e50d95a4dce53517934367122f6872.png

1. 使用 rs.next() 偏移光标,循环指定具体的某一行

获取数据的具体方法

590de5cd46897b31413313c0d1ed7a4c.png
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

//DQL :查询操作
public class D_DQLDemo {
	
	/*
	 * 多行查询 :查询t_student表中的所有数据
	 * SQL : select * from t_student
	 */
	@Test
	public void testList() throws Exception {
		String sql = "select * from t_student";
		
		// 1.加载注册驱动
		Class.forName("com.mysql.jdbc.Driver");

		// 2.获取数据库连接对象
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
		// 3.创建语句对象
		Statement st = conn.createStatement();
		
		// 4.执行SQL语句
		// int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数
		// ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象
		ResultSet rs = st.executeQuery(sql);
		
		
		//创建list集合用于封装Student对象
		List<Student> stus = new ArrayList<>();
		
		while(rs.next()) {
			//1.通过结果集的位置获取对应的数
			/*Object id = rs.getObject(1);
			Object name = rs.getObject(2);
			Object age = rs.getObject(3);*/
			
			//2.通过结果集的 列名获取对应的数据
			/*Object id = rs.getObject("id");
			Object name = rs.getObject("name");
			Object age = rs.getObject("age");*/
			//3.通过数据库数据和Java对应的数据类型获取对应的只
			int id = rs.getInt("id");
			String name = rs.getString("name");
			int age = rs.getInt("age");
			//System.out.println(id+","+name+","+age);
			
			//将获取的数据封装成对应的Student对象
			Student stu = new  Student(id, name, age);
			
			//将一个个Student对象添加到list集合中
			stus.add(stu);
		}
		
		for (Student student : stus) {
			System.out.println(student);
		}
		//5.释放资源(先开后关)
		rs.close();
		st.close();
		conn.close();
	}
	
	
	/*
	 * 单行查询: 查询出t_student 指定id的信息
	 * SQL : select * from t_student where id = 1;
	 */
	@Test
	public void testGetOne() throws Exception {
		String sql = "select * from t_student where id = 2";
		
		// 1.加载注册驱动
		Class.forName("com.mysql.jdbc.Driver");

		// 2.获取数据库连接对象
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
		// 3.创建语句对象
		Statement st = conn.createStatement();
		
		// 4.执行SQL语句
		// int rows = st.executeUpdate(String sql);执行DDL和DML语句,放回的是受影响的行数
		// ResultSet res = st.executeQuery(String sql);执行DQL查询语句,返回的结果集对象
		ResultSet rs = st.executeQuery(sql);
		
		if(rs.next()) {
			//1.通过结果集的位置获取对应的数
			/*Object id = rs.getObject(1);
			Object name = rs.getObject(2);
			Object age = rs.getObject(3);*/
			
			//2.通过结果集的 列名获取对应的数据
			/*Object id = rs.getObject("id");
			Object name = rs.getObject("name");
			Object age = rs.getObject("age");*/
			//3.通过数据库数据和Java对应的数据类型获取对应的只
			int id = rs.getInt("id");
			String name = rs.getString("name");
			int age = rs.getInt("age");
			//System.out.println(id+","+name+","+age);
			
			//将获取的数据封装成对应的Student对象
			Student stu = new  Student(id, name, age);
			System.out.println(stu);
		}
		//5.释放资源(先开后关)
		rs.close();
		st.close();
		conn.close();	
	}
}

6. 预编译语句对象PreparedStatment

问题 : 我们有了Statment对象可以执行SQL,为什么还要使用PreparedStatment?

优势

1. SQL语句结构清晰,参数的设置和SQL语句分离

2. 性能更高

3. 防止SQL注入

Statement: 表示静态SQL语句对象.

PreparedStatement:Statement的子接口,表示预编译SQL语句对象. 通过占位符(?)来拼SQL.

6.1 创建PreparedStatement

创建语句对象 Statment

d95693c19afd3a76323c22fbb87bffc1.png

f0248bb7f7db9b054da92e2266c15a52.png

6.2 执行SQL语句的方法

6.2.1 Statement

在执行SQL语句的时候会带上SQL语句

143fcaa2bda285751bbd6a16baf86363.png

6.2.2 PreparedStatemeny

在执行SQL语句的方法中不需要设置SQL语句

3e6793054a58308954343e0cccf735f1.png

6.3 设置站位参数的值

void setXxx(int parameterIndex,Xxx value):用于设置占位符参数,

parameterIndex:第几个问号. 注意:从1开始.

value:设置的真实值.

Xxx:表示数据类型.String/int/long/Double/Date

6.4 代码

import static org.junit.Assert.*;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

import org.junit.Test;

//DML : 对表数据的增删改操作,使用预编译语句对象
public class E_DMLByPreparedStatmentDemo {

	/*
	 * 向 t_student表中插入一条数据 
	 * sql : insert into t_student(name,age) values ('乔峰',30)
	 */
	@Test
	public void testInsert() throws Exception {
		String sql = "insert into t_student(name,age) values (?,?)";
		
		// 1.加载注册驱动
		Class.forName("com.mysql.jdbc.Driver");

		// 2.获取数据库连接对象
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
		// 3.创建预编译语句对象
		PreparedStatement ps = conn.prepareStatement(sql);
		//3.1设置占位符参数
		ps.setString(1, "东方姑娘");
		ps.setInt(2, 18);
		
		// 4.执行SQL语句:注意不要带SQL参数
		ps.executeUpdate();
		//5.释放资源(先开后关)
		ps.close();
		conn.close();

	}
	/*
	 * 删除操作: 删除t_student表中的某一条数据
	 * SQL :delete from t_student where id = 2
	 */
	@Test
	public void testDelete() throws Exception {
		String sql = "delete from t_student where id = ?";
		
		// 1.加载注册驱动
		Class.forName("com.mysql.jdbc.Driver");

		// 2.获取数据库连接对象
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
		// 3.创建预编译语句对象
		PreparedStatement ps = conn.prepareStatement(sql);
		//3.1设置占位符对应的参数值
		ps.setInt(1, 1);
		
		// 4.执行SQL语句
		int rows = ps.executeUpdate();
		System.out.println(rows);
		//5.释放资源(先开后关)
		ps.close();
		conn.close();
	}
	/*
	 * 修改操作 : 修改t_student表中的某一条数据
	 * SQL : update t_student set name = '虚竹',age = 50 where id = 3
	 */
	@Test
	public void testUpdate() throws Exception {
		String sql = "update t_student set name = ?,age = ? where id = ?";
		
		// 1.加载注册驱动
		Class.forName("com.mysql.jdbc.Driver");

		// 2.获取数据库连接对象
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
		// 3.创建预编译语句对象
		PreparedStatement ps = conn.prepareStatement(sql);
		//3.1设置占位符参数对应的值
		ps.setString(1, "西方失败");
		ps.setInt(2, 40);
		ps.setInt(3, 4);
		// 4.执行SQL语句
		int rows = ps.executeUpdate();
		System.out.println(rows);
		//5.释放资源(先开后关)
		ps.close();
		conn.close();
	}
}

7. JavaWeb开发的分层设计-三层架构

实际开发中,JavaWeb开发代码一般分为三层,分层结构是JavaWeb开发中的一种设计思想,这样会让我们开发层次分明,每一层只要完成对应的功能即可,使得项目便于开发和维护

1 . Web层/表现层 : 主要接受前台浏览器用户的参数,给浏览器响应数据等等

2. Service层/业务成/服务层:主要处理业务功能,日志,权限,事物,等等

3. DAO层/持久层 :专门负责和数据库交互,数据处理相关代码

DAO : Data Access Object 数据访问对象

实际开发中 : 用户请求到-Web层--->Service层-->DAO层

7.2 DAO思想

37632972aa026a913321c60481db7c7b.png

7.3 使用DAO以后代码的以及包的设计结构

开发中如果使用的分层,编写的包和类名接口名等等都是有固定规则,不能随便瞎写

7.3.1 DAO层接口包命名

公司域名倒写+项目名称/模块名称+dao

如 : cn.sxt.crm.dao

7.3.2 DAO层实现类包命名

公司域名倒写+项目名称/模块名称+dao+impl

如 : cn.sxt.crm.dao.impl

7.3.3 DAO层操作对应表的接口命名

对应表的名称 + Dao/DAO

如 : StudentDao/DAO , TeacherDao/DAO

7.3.4 DAO层操作对应表的实现类命名

对应表的名称 + Dao/DAOImpl

如 : StudentDaoImpl/DAOImpl , TeacherDaoImpl/DAOImpl

7.3.5 数据表对应的Java类domain/pojo包命名

POJO(Plain Ordinary Java Object)简单的Java对象
domian : 域对象

公司域名倒写+项目名称/模块名称+domain/pojo

如 : cn.sxt.crm.domain

7.3.6 对应的测试包命名

公司域名倒写+项目名称/模块名称+test

如 : cn.sxt.crm.test

7.3.7 项目的工具类包命名

公司域名倒写+项目名称/模块名称+util/utils

如 : cn.sxt.crm.util/utils

7.3.8 DAO代码设计结构

fae3876149e78ffc0bc30ac15453a288.png

7.3.9 Dao的实现类代码

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import cn.sxt.jdbc.dao.StudentDao;
import cn.sxt.jdbc.domain.Student;

public class StudentDaoImpl implements StudentDao {

	@Override
	public int saveStudent(Student stu) {
		String sql = "insert into t_student(name,age) values (?,?)";
		
		Connection conn = null;
		PreparedStatement ps = null;
		try {
			
			// 1.加载注册驱动
			Class.forName("com.mysql.jdbc.Driver");

			// 2.获取数据库连接对象
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
			// 3.创建预编译语句对象
			ps = conn.prepareStatement(sql);
			//3.1设置占位符参数
			ps.setString(1, stu.getName());
			ps.setInt(2, stu.getAge());
			
			// 4.执行SQL语句:注意不要带SQL参数
			return ps.executeUpdate();
			
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			//5.释放资源(先开后关)
			try {
				if(ps !=null) {
					ps.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}finally {
				try {
					if(conn !=null) {
						conn.close();
					}
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		return 0;
	}

	@Override
	public int deleteById(int id) {
		
		String sql = "delete from t_student where id = ?";
		
		Connection conn = null;
		PreparedStatement ps = null;
		try {
			
			// 1.加载注册驱动
			Class.forName("com.mysql.jdbc.Driver");

			// 2.获取数据库连接对象
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
			// 3.创建预编译语句对象
			ps = conn.prepareStatement(sql);
			//3.1设置占位符参数
			ps.setInt(1, id);
			
			// 4.执行SQL语句:注意不要带SQL参数
			return ps.executeUpdate();
			
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			//5.释放资源(先开后关)
			try {
				if(ps !=null) {
					ps.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}finally {
				try {
					if(conn !=null) {
						conn.close();
					}
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		return 0;
	}

	@Override
	public int updateStudentById(Student stu) {
		
		String sql = "update t_student set name = ?,age = ? where id = ?";
		
		Connection conn = null;
		PreparedStatement ps = null;
		try {
			
			// 1.加载注册驱动
			Class.forName("com.mysql.jdbc.Driver");

			// 2.获取数据库连接对象
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
			// 3.创建预编译语句对象
			ps = conn.prepareStatement(sql);
			//3.1设置占位符参数
			ps.setString(1, stu.getName());
			ps.setInt(2, stu.getAge());
			ps.setInt(3, stu.getId());
			// 4.执行SQL语句:注意不要带SQL参数
			return ps.executeUpdate();
			
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			//5.释放资源(先开后关)
			try {
				if(ps !=null) {
					ps.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}finally {
				try {
					if(conn !=null) {
						conn.close();
					}
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		return 0;
	}

	@Override
	public Student selectById(int id) {
		String sql = "select * from t_student where id = ?";
		
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		
		try {
			// 1.加载注册驱动
			Class.forName("com.mysql.jdbc.Driver");

			// 2.获取数据库连接对象
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
			// 3.创建语句对象
			ps = conn.prepareStatement(sql);
			//3.1设置占位符参数对应的值
			ps.setInt(1, id);
			
			// 4.执行SQL语句
			rs = ps.executeQuery();
			if(rs.next()) {
				//通过数据库数据和Java对应的数据类型获取对应的只
				String name = rs.getString("name");
				int age = rs.getInt("age");
				//System.out.println(id+","+name+","+age);
				
				//将获取的数据封装成对应的Student对象
				Student stu = new  Student(id, name, age);
				
				return stu;
			}
			
		} catch (Exception e) {
			// TODO: handle exception
		}finally {
			try {
				if(rs !=null) {
					rs.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}finally {
				try {
					if(ps !=null) {
						ps.close();
					}
				} catch (SQLException e) {
					e.printStackTrace();
				}finally {
					try {
						if(conn !=null) {
							conn.close();
						}
					} catch (SQLException e) {
						e.printStackTrace();
					}
				}
			}
		}
		
		return null;
	}

	@Override
	public List<Student> selectList() {
		String sql = "select * from t_student";
		//创建list集合用于封装Student对象
		List<Student> stus = new ArrayList<>();
		
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		
		try {
			
			// 1.加载注册驱动
			Class.forName("com.mysql.jdbc.Driver");

			// 2.获取数据库连接对象
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo", "root", "root");
			// 3.创建语句对象
			ps = conn.prepareStatement(sql);
			
			// 4.执行SQL语句
			rs = ps.executeQuery();
			while(rs.next()) {
				//通过数据库数据和Java对应的数据类型获取对应的只
				int id = rs.getInt("id");
				String name = rs.getString("name");
				int age = rs.getInt("age");
				//System.out.println(id+","+name+","+age);
				
				//将获取的数据封装成对应的Student对象
				Student stu = new  Student(id, name, age);
				//将一个个Student对象添加到list集合中
				stus.add(stu);
			}
			
		} catch (Exception e) {
			// TODO: handle exception
		}finally {
			try {
				if(rs !=null) {
					rs.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}finally {
				try {
					if(ps !=null) {
						ps.close();
					}
				} catch (SQLException e) {
					e.printStackTrace();
				}finally {
					try {
						if(conn !=null) {
							conn.close();
						}
					} catch (SQLException e) {
						e.printStackTrace();
					}
				}
			}
		}
		
		return stus;
	}

}

7.3.10 快速生成单元测试类

一个dao层或者service编写代码以后,需要为每一个功能都进行单元测试,一个dao中的方法很多。我们快速为这个dao层的类生成单元测试类,(dao的每一个方法都自动生成一个测试方法)

7722da0c056fe5c884000b4424e09288.png

a66b1594faf835349c38d21a1eaa924a.png

00f1054de8ab268e80edeafa0cbb8eb0.png

187a6f6faa57bd501038b53c99910e1c.png

7.4 代码初步重构

上述的DAO方法中的代码,存在的问题:

问题1:每个DAO方法中都会写:驱动名称/url/账号/密码,不利于维护.

解决方案: 声明为成员变量即可.(在被类中任何地方都可以访问)

问题2:问题1的解决方案有问题.

每个DAO实现类里都有一模一样的4行代码,不利于维护(考虑有100个DAO实现类,就得重复99次).

解决方案: 把驱动名称/url/账号/密码这四行代码,专门抽取到一个JDBC的工具类中.---->JdbcUtil.

问题3:其实DAO方法,每次操作都只想需要Connection对象即可,而不关心是如何创建的.

解决方案:把创建Connection的代码,抽取到JdbcUtil中,并提供方法getConn用于向调用者返回Connection对象即可.

问题4:每次调用者调用getConn方法的时候,都会创建一个Connection对象.

但是,每次都会加载注册驱动一次.--->没必要的.

解决方案:把加载注册驱动的代码放在静态代码块中--->只会在所在类被加载进JVM的时候,执行一次.

问题5:每个DAO方法都要关闭资源.(鸡肋代码).

解决方案:把关闭资源的代码,抽取到JdbcUtil中.

public static void close(Connection conn, Statement st, ResultSet rs) {}

调用者:

DML: JdbcUtil.close(conn,st,null);

DQL: JdbcUtil.close(conn,st,rs);

问题6 :连接数据库的账号密码写死在JdbcUtil工具类中了,不利于维护

抽取 db.properties 配置文件,将数据库对应的账号密码写到配置文件中,然后使用程序读取配置文件内容即可

7.4.1 jdbcUtil 工具类

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;


public class JdbcUtil {

	// alt+shif+a 多行修改,修改以后还原 alt+shif+a

	/*private static String driverClassName = "com.mysql.jdbc.Driver";
	private static String url = "jdbc:mysql://localhost:3306/jdbcdemo";
	private static String username = "root";
	private static String password = "root";*/
	
	private static Properties p = new Properties();
	
	static {
		try {
			//1.获取类加载器
			ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
			//2,使用类加载器获取项目 类路径下面的文件
			InputStream inputStream = classLoader.getResourceAsStream("db.properties");
			
			//3.使用Priperties加载配置文件对应的输入流
			p.load(inputStream);
			
			Class.forName(p.getProperty("driverClassName"));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static Connection getConnection() {
		try {
			
			return DriverManager.getConnection(p.getProperty("url"), p.getProperty("username"), p.getProperty("password"));
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException("亲,连接数据库失败", e);
		}
	}
	
	public static void close(Connection conn,PreparedStatement ps,ResultSet rs) {
		try {
			if(rs !=null) {
				rs.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally {
			try {
				if(ps !=null) {
					ps.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}finally {
				try {
					if(conn !=null) {
						conn.close();
					}
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

7.4.2 使用工具类以后的DAO实现类效果

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import cn.sxt.jdbc.dao.StudentDao;
import cn.sxt.jdbc.domain.Student;
import cn.sxt.jdbc.util.JdbcUtil;

public class StudentDaoImpl implements StudentDao {
	
	

	@Override
	public int saveStudent(Student stu) {
		String sql = "insert into t_student(name,age) values (?,?)";
		
		Connection conn = null;
		PreparedStatement ps = null;
		try {
			conn = JdbcUtil.getConnection();
			
			// 3.创建预编译语句对象
			ps = conn.prepareStatement(sql);
			//3.1设置占位符参数
			ps.setString(1, stu.getName());
			ps.setInt(2, stu.getAge());
			
			// 4.执行SQL语句:注意不要带SQL参数
			return ps.executeUpdate();
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			JdbcUtil.close(conn, ps, null);
		}
		return 0;
	}

	@Override
	public int deleteById(int id) {
		
		String sql = "delete from t_student where id = ?";
		
		Connection conn = null;
		PreparedStatement ps = null;
		try {
			
			conn = JdbcUtil.getConnection();
			// 3.创建预编译语句对象
			ps = conn.prepareStatement(sql);
			//3.1设置占位符参数
			ps.setInt(1, id);
			
			// 4.执行SQL语句:注意不要带SQL参数
			return ps.executeUpdate();
			
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			JdbcUtil.close(conn, ps, null);
		}
		return 0;
	}

	@Override
	public int updateStudentById(Student stu) {
		
		String sql = "update t_student set name = ?,age = ? where id = ?";
		
		Connection conn = null;
		PreparedStatement ps = null;
		try {
			
			conn = JdbcUtil.getConnection();
			// 3.创建预编译语句对象
			ps = conn.prepareStatement(sql);
			//3.1设置占位符参数
			ps.setString(1, stu.getName());
			ps.setInt(2, stu.getAge());
			ps.setInt(3, stu.getId());
			// 4.执行SQL语句:注意不要带SQL参数
			return ps.executeUpdate();
			
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			JdbcUtil.close(conn, ps, null);
		}
		return 0;
	}

	@Override
	public Student selectById(int id) {
		String sql = "select * from t_student where id = ?";
		
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		
		try {
			conn = JdbcUtil.getConnection();
			// 3.创建语句对象
			ps = conn.prepareStatement(sql);
			//3.1设置占位符参数对应的值
			ps.setInt(1, id);
			
			// 4.执行SQL语句
			rs = ps.executeQuery();
			if(rs.next()) {
				//通过数据库数据和Java对应的数据类型获取对应的只
				String name = rs.getString("name");
				int age = rs.getInt("age");
				//System.out.println(id+","+name+","+age);
				//将获取的数据封装成对应的Student对象
				Student stu = new  Student(id, name, age);
				return stu;
			}
			
		} catch (Exception e) {
			// TODO: handle exception
		}finally {
			try {
				if(rs !=null) {
					rs.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}finally {
				JdbcUtil.close(conn, ps, rs);
			}
		}
		
		return null;
	}

	@Override
	public List<Student> selectList() {
		String sql = "select * from t_student";
		//创建list集合用于封装Student对象
		List<Student> stus = new ArrayList<>();
		
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		
		try {
			
			conn = JdbcUtil.getConnection();
			// 3.创建语句对象
			ps = conn.prepareStatement(sql);
			
			// 4.执行SQL语句
			rs = ps.executeQuery();
			while(rs.next()) {
				//通过数据库数据和Java对应的数据类型获取对应的只
				int id = rs.getInt("id");
				String name = rs.getString("name");
				int age = rs.getInt("age");
				//System.out.println(id+","+name+","+age);
				
				//将获取的数据封装成对应的Student对象
				Student stu = new  Student(id, name, age);
				//将一个个Student对象添加到list集合中
				stus.add(stu);
			}
			
		} catch (Exception e) {
			// TODO: handle exception
		}finally {
			JdbcUtil.close(conn, ps, rs);
		}
		
		return stus;
	}

}

7.5 知识点补充,类加载器

在项目的 类路径(src)下面创建一个 db.properties配置文件,专门配置连接数据库的账号密码

如何使用类加载器加载配置文件

7.5.1 配置文件

e064f9c704c3d4f09b16c405429cf0f2.png

7.5.1.1 配置文件创建的位置

配置文件一般都放在项目的src 源目录下面

bcad7f4d377f9110c3661c9f182376b6.png

7.5.2 加载代码

import static org.junit.Assert.*;
import java.io.InputStream;
import java.util.Properties;
import org.junit.Test;

public class PropertiesTest {
	
	@Test
	public void testName() throws Exception {
		
		 /*
		  * ClassLoader 类加载器
		  * ClassLoader :可以从项目的类路径下面读取对应的配置文件返回一个输入流
		  * ClassLoader 在程序运行的时候JVM已经为每一个项目都创建了一个,我们开发者只需要获取即可
		  * 获取类加载器方式
		  * 1、使用当前线程
		  * ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		  * 2、通过某一类的字节码实例也可以获取
		  * ClassLoader classLoader = PropertiesTest.class.getClassLoader();
		  */
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		//使用类加载器获取项目 类路径下面的文件
		InputStream inputStream = classLoader.getResourceAsStream("db.properties");
		
		
		/*
		 * Properties 是Map集合下面的一个 专门用于读取配置文件的对象
		 * 可以读取当前类路径下面的  xxx.properites类型的配置文件
		 * 
		 * xxx.properites的内容必须是key=value 键值对的数据
		 */
		
		//1.创建Properties对象
		Properties p = new Properties();
		
		//2.加载配置文件
		p.load(inputStream);
		
		System.out.println(p);
		
		//获取具体某一个key对应的值
		String driverClassName = p.getProperty("driverClassName");
		System.out.println(driverClassName);
	}
}

7.5.3 效果

e71f3b2975398721b9d0715a0b4fbfa5.png

8. 连接池

8.1 遇到的问题-引出连接池

4ee2db07d1e867bc7c360c6302ee925e.png

8.2 连接池思想

c66029446bb4efae660b637bc0d91eb0.png

8.3 连接池的概述

在Java中,连接池使用javax.sql.DataSource接口来表示连接池.

注意:DataSource仅仅只是一个接口,由各大服务器厂商来实现(Tomcat.JBoss,阿里巴巴).

常用的DataSource的实现:

DBCP: Spring推荐的

C3P0: Hibernate推荐的

Druid : (德鲁伊)阿里巴巴开源的,性能最好,速度最快

DataSource(数据源)和连接池(Connection Pool)是同一个。

8.4 使用连接池和不使用连接池的区别在哪里

从代码上:

不使用连接池: Conenction对象由DriverManager获取.

Connection conn = DriverManager.getConnection(url,username,password);

使用连接池:

如何创建DataSource对象,如何在DataSource中设置url,账号,密码.

Connection conn = DataSource对象.getConnection();

--------------------------------------------------------------------

使用连接池的时候:

释放资源: Connection对象.close():

是把Connection放回给连接池,而不是和数据库断开.

8.5 Druid连接池的使用

8.5.1 准备druid 连接池jar包到项目

03691d7686879fe01b5a178a08b8b92f.png
import static org.junit.Assert.*;

import java.io.InputStream;
import java.io.Reader;
import java.sql.Connection;
import java.util.Properties;

import javax.sql.DataSource;

import org.junit.Test;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.alibaba.druid.pool.DruidPooledConnection;

public class DataSourceTest {
	// 直接创建连接池对象
	@Test
	public void testName() throws Exception {
		// 1.创建连接池对象
		DruidDataSource ds = new DruidDataSource();
		// 2.设置连接数据库的账号密码
		ds.setDriverClassName("com.mysql.jdbc.Driver");
		ds.setUrl("jdbc:mysql://localhost:3306/jdbcdemo");
		ds.setUsername("root");
		ds.setPassword("root");
		ds.setMaxActive(10);// 最大连接数
		// 3.获取连接对象
		Connection conn = ds.getConnection();
		System.out.println(conn);
	}

	// 使用工厂对象创建连接池对象,工厂对象的好处,不需要直接设置账号密码等等,只需要将
	// 连接数据库的账号密码等等以指定的 key的名称配置到 xxx.properties文件中即可,工厂对象底层自动读取
	@Test
	public void testDataSourceByFactory() throws Exception {

		// 1.获取类加载器用于加载clsspath下面的 配置文件
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// 2.读取druid.properties配置文件
		InputStream inputStream = classLoader.getResourceAsStream("druid.properties");
		// 3.创建Properties对象,并读取配置文件对应的输入流
		Properties p = new Properties();
		p.load(inputStream);

		// 4.创建连接池对象
		DataSource ds = DruidDataSourceFactory.createDataSource(p);
		// 5.获取连接对象
		Connection conn = ds.getConnection();
		System.out.println(conn);
	}
}

00e404cd09e11b7dc893c98ddd4d75ab.png

8.5.2 db.propperties

0652da55e00b3515199ac0b315853d56.png

8.5.3 使用Druid抽取的工具类

921b5b884f0c98f3e23392984552f6f2.png

9. 事务

案例:银行转账:从张无忌账户上给赵敏转1000块.

准备:account(账户表):

---------------------------------------------------------------

id name(账号,唯一) balance(余额)

1 张无忌 20000

2 赵敏 0

---------------------------------------------------------------

转账的思路:

1.检查张无忌的账号余额是否大于等于1000.

SQL: SELECT balance FROM account WHERE name = '张无忌' AND balance >=1000

余额>=1000:GOTO 2:

余额 <1000:提示:亲,你的余额不足.

2.在张无忌的账号余额上减少1000.

SQL: UPDATE account SET balance = balance-1000 WHERE name = '张无忌'

3.在赵敏的账户余额尚增加1000.

SQL: UPDATE account SET balance = balance+1000 WHERE name = '赵敏'

-------------------------------------------------------------------------------------------

注意:在第二步和第三步之间,停电了.

使用异常模拟停电:System.out.println(1/0);

9.1 事务概述

事务(Transaction,简写为tx):

在数据库中,所谓事务是指一组逻辑操作单元,使数据从一种状态变换到另一种状态。

为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:

当每个逻辑操作单元全部完成时,数据的一致性可以保持,

而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。

事务的操作:先定义开始一个事务,然后对数据作修改操作,这时如果提交(commit),这些修改就永久地保存下来,如果回退(rollback),数据库管理系统将放弃您所作的所有修改而回到开始事务时的状态。

--------------------------------------------------

事务的ACID属性:

1. 原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

2. 一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。(数据不被破坏)

3. 隔离性(Isolation)

事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

4. 持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响

--------------------------------------------------

事务:指构成单个逻辑工作单元的操作集合

事务处理:保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),要么整个事务回滚(rollback)到最初状态

处理事务的两个动作:

提交:commit: 当整个事务中,所有的逻辑单元都正常执行成功. ---->提交事务.---数据已经提交,不能更改.

回滚:rollback: 当整个事务中,有一个逻辑单元执行失败, ---->回滚事务.

撤销该事务中的所有操作--->恢复到最初的状态.

---------------------------------------------------------------------------------------------------

如何在代码中去处理事务:

1.在JDBC中,事务是默认自动提交的. 必须先设置事务为手动提交.

connection对象.setAutoCommit(false);//设置事务为手动提交.

2.手动的提交事务.

connection对象.commit();

3.若出现异常必须回滚事务:

不回滚事务,总余额依然是正确的. 若不回滚事务,不会释放数据库资源.

connection对象.rollback();

-----------------------------------------------------------------------------------

1.在JDBC在事务是默认提交的,那是在什么时候提交的.

在执行一个DML/DDL操作的时候,就已经提交事务了.

2.针对于CRUD操作. 只有DML操作才有事务,查询操作没有事务.

但是,我们一般会把查询也放在事务里面.

4. 以后,凡是发现自己编写的代码是正确的,测试也通过,但是就是数据库表中的数据不变----->事务没提交的问题.

4.MySQL中,InnoDB支持外键.支持事务,MyISAM不支持外键,不支持事务.

cc79750a0415cb0d1d13e5ccb3b67304.png

InnoDB存储引擎: 支持事务,支持外键,但是查询效率略低,(金融,理财,p2p)

MyISAM存储引擎:不支持事务和外键,但是查询效率较高(新闻网站)

Oracle 不存在存储引擎,都有事务

9.2 事务处理代码

public class TransactionTest {
	@Test
	public void testName() throws Exception {
		Connection conn = null;
		Statement st = null;
		ResultSet rs = null;
		try {
			conn = DruidUtil.getConnection();
			//将事务设置为手动提交
			conn.setAutoCommit(false);
			
			st = conn.createStatement();
			// 1.检查张无忌的账号余额是否大于等于1000.
			rs = st.executeQuery("SELECT balance FROM account WHERE name = '张无忌' AND balance >=1000");
			if(!rs.next()) {
				throw new RuntimeException("亲,您的账户余额不够");
			}
			// 余额>=1000:GOTO 2:
			// 余额 <1000:提示:亲,你的余额不足.
			// 2.在张无忌的账号余额上减少1000.
			st.executeUpdate("UPDATE account SET balance = balance-1000 WHERE name = '张无忌'");
			
			System.out.println(1/0);
			
			// 3.在赵敏的账户余额尚增加1000.
			st.executeUpdate("UPDATE account SET balance = balance+1000 WHERE name = '赵敏'");
			
			//提交事务
			conn.commit();
		
			
		} catch (Exception e) {
			e.printStackTrace();
			//回滚事务
			conn.rollback();
			
		}finally {
			DruidUtil.close(conn, st, rs);
		}
		
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值