我们在Java程序或JavaWeb程序中如果想要连接到数据库,那么该怎么实现呢?Java提供了JDBC API来帮助用户进行数据库连接操作。如下图:
据图可以看到,JDBC API是连接Java程序和各个数据库产品的桥梁。想要连接上数据库,必须要通过JDBC API接口,JDBC API是Java程序提供的连接第三方数据库的唯一途径。
JDBC的全称是Java DataBase Connectivity,即Java数据库连接,它是一套面向对象的应用程序接口,指定了统一的访问各种关系型数据库的标准接口。JDBC是一种底层的API,因此访问数据库时必须在业务逻辑层中嵌入SQL语句。因为SQL语句时面向关系的,依赖于关系模型,所以通过JDBC技术访问数据库也是面向关系的。
JDBC技术主要完成的任务如下:
提示:JDBC并不能直接访问数据库,它只是提供统一访问的接口,至于实现,完全交给数据库厂商自己来实现,也就是各个厂商提供的JDBC驱动程序。简单来说,就是数据库厂商你想要支持我的Java语言,那么必须把JDBC中的接口给我实现了,这是想要用我的东西的一个前提,不实现也可以,那么对不起,Java不支持你的数据库产品。
核心类和接口如下:
JDBC中最核心的就是这1个类和另外的5个接口了,可以说非常常用,因此有必要十分熟悉。以下会对它们进行逐一说明。
这个类是用来管理JDBC驱动程序的。它是JDBC的管理层,作用于用户和驱动程序之间。直接看api文档说明:
提供的方法如下:
这个类中我们常用的方法只有一个,getConnection,一般是用有3个参数的,说明如下:
这个接口是关于数据库连接的。它代表与特定的数据库的连接,在连接上下文中执行SQL语句并返回结果。api文档说明如下:
提供的方法如下:
标记的是常用方法,说明如下:
以下将使用DriverManager类来获取一个Connection连接,用于连接上本地的Oracle数据库,以及测试Connection接口的一些方法。
先把这个ojdbc6的驱动包放进去,其他两个包先无视。这个驱动包是Oracle厂商自己提供的驱动包,是对Java提供的JDBC API中接口的实现,没有这个驱动是连不上的。然后右键build path,把它加进Libraries中,如下:
要保证驱动加到里面了。
然后开始写代码:
/*
* 测试Oracle数据库连接
*/
public class TestConnection {
// Oracle数据库驱动包位置
private static final String ORACLE_DRIVER = "oracle.jdbc.OracleDriver";
// 数据库连接的url
private static final String CONNECTION_URL = "jdbc:oracle:thin:@localhost:1521:orcl";
// 用户名
private static final String USERNAME = "yanchengzhi";
// 密码
private static final String PASSWORD = "ycz951824";
public static void main(String[] args) {
try {
// 显示加载Oracle的驱动包
Class.forName(ORACLE_DRIVER);
// 获取Connection连接
Connection connection = DriverManager.getConnection(CONNECTION_URL, USERNAME, PASSWORD);
if (connection != null) {
System.out.println("Oracle数据库连接成功!");
System.out.println("数据库连接是否关闭?" + connection.isClosed());
System.out.println(connection);
// 手动关闭连接
connection.close();
if (connection.isClosed()) {
System.out.println("已经手动关闭数据库连接!");
}
} else {
System.out.println("数据库连接失败!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行这个程序:
那么以上测试就通过了,成功连接到了Oracle数据库。需要说明的是,连接最后一定要关闭,就像流一样,如果不关,可能会耗尽系统的资源。
这个接口用于执行静态的SQL语句,即是不带参数的SQL语句,并返回生成结果的对象。api文档说明如下:
提供的方法如下:
常用方法说明如下:
这个接口是Statement接口的子接口,表示的是预编译的SQL语句对象,执行的是动态的SQL语句,即带参数的SQL语句。api文档说明如下:
提供的方法如下:
常用方法说明如下:
(1)数据库配置文件
数据库连接信息通常以普通文本属性文件进行配置,如xxxxx.properties。新建一个conf包,包下创建一个properties属性文件。
文件内容如下:
## 数据库连接url
jdbc_url=jdbc:oracle:thin:@localhost:1521:orcl
## 数据库连接驱动
jdbc_driver=oracle.jdbc.OracleDriver
## 用户
jdbc_username=yanchengzhi
## 密码
jdbc_password=ycz951824
(2)获取配置文件的属性值
新建一个utils包,包下创建一个Env类,如下:
/*
* 工具类,用于获取数据库的连接信息
* 需要继承自java.util.Properties类
*/
public class Env extends Properties {
private static final long serialVersionUID = 4182699870347430944L;
// 属性文件的位置
private static final String DB_CONF_FILE_PATH = "com/ycz/conf/oracle_db_conf.properties";
// 声明一个此类对象
private static Env env;
// 声明4个静态常量
public static final String JDBC_URL;
public static final String JDBC_DRIVER;
public static final String JDBC_USERNAME;
public static final String JDBC_PASSWORD;
// 静态代码块统一赋值
static {
// 获取Env实例
getEnv();
// 获取属性文件的输入流对象
InputStream inputStream = env.getClass().getClassLoader().getResourceAsStream(DB_CONF_FILE_PATH);
try {
// 加载输入流
env.load(inputStream);
// 关闭输入流
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
// 获取属性值
JDBC_URL = env.getProperty("jdbc_url");
JDBC_DRIVER = env.getProperty("jdbc_driver");
JDBC_USERNAME = env.getProperty("jdbc_username");
JDBC_PASSWORD = env.getProperty("jdbc_password");
}
// 实例化此类对象,单例模式
private static void getEnv() {
if (env == null) {
env = new Env();
}
}
}
测试是否能获取到属性值:
public static void main(String[] args) {
// 测试属性值是否获取到
System.out.println("连接的URL:" + Env.JDBC_URL);
System.out.println("数据库驱动:" + Env.JDBC_DRIVER);
System.out.println("用户:" + Env.JDBC_USERNAME);
System.out.println("数据库的连接密码:" + Env.JDBC_PASSWORD);
}
(3)数据源管理组件
定义一个数据源的管理组件,不使用数据库连接池。在utils包下创建新的类DataSourceManager,如下:
/*
* 工具类
* 进行数据源的管理
*/
public final class DataSourceManager {
// 定义一个方法,获取Connection连接
public static Connection getConnection() {
Connection connection = null;
try {
// 加载数据库驱动
Class.forName(Env.JDBC_DRIVER);
// 获取连接
connection = DriverManager.getConnection(Env.JDBC_URL, Env.JDBC_USERNAME, Env.JDBC_PASSWORD);
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
// 定义一个方法,关闭Connection连接
public static void closeConnection(Connection connection) {
try {
if (connection != null && !connection.isClosed()) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
// 定义一个方法,用于关闭Statement接口
public static void closeStatement(Statement statement) {
try {
if (statement != null && !statement.isClosed()) {
statement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
// 定义一个方法,用于关闭ResultSet接口
public static void closeResultSet(ResultSet resultSet) {
try {
if (resultSet != null && !resultSet.isClosed()) {
resultSet.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
再定义一个数据源管理组件,使用c3p0的连接池,先要将包加进来:
第一个是c3p0的连接池包,第二个是辅助包。
在utils包下创建新的类DataSourceForPool,如下:
/*
* 数据源管理组件
* 使用c3p0连接池
*/
public class DataSourceForPool {
// 声明一个数据源对象
private static ComboPooledDataSource c3p0;
// 定义一个方法,创建数据源对象
public static void createDataSource() {
if (c3p0 == null) {
c3p0 = new ComboPooledDataSource();
}
try {
c3p0.setJdbcUrl(Env.JDBC_URL);// 设置连接URL
c3p0.setDriverClass(Env.JDBC_DRIVER);// 设置驱动
c3p0.setUser(Env.JDBC_USERNAME);// 设置用户
c3p0.setPassword(Env.JDBC_PASSWORD);// 设置密码
c3p0.setCheckoutTimeout(30000);// 设置超时时间
c3p0.setMaxPoolSize(20);// 设置最大连接池数
c3p0.setDataSourceName("ycz");// 设置连接池名称
} catch (PropertyVetoException e) {
e.printStackTrace();
}
}
// 定义一个方法,获取Connection连接
public static Connection getConnection() {
Connection connection = null;
try {
// 加载数据库驱动
Class.forName(Env.JDBC_DRIVER);
// 获取连接
connection = DriverManager.getConnection(Env.JDBC_URL, Env.JDBC_USERNAME, Env.JDBC_PASSWORD);
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
// 定义一个方法,关闭Connection连接
public static void closeConnection(Connection connection) {
try {
if (connection != null && !connection.isClosed()) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
// 定义一个方法,用于关闭Statement接口
public static void closeStatement(Statement statement) {
try {
if (statement != null && !statement.isClosed()) {
statement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
// 定义一个方法,用于关闭ResultSet接口
public static void closeResultSet(ResultSet resultSet) {
try {
if (resultSet != null && !resultSet.isClosed()) {
resultSet.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
以上的准备工程就完成了,下面进行写增删改的代码。
现在准备使用JDBC对下面这张表进行记录的添加操作:
8条记录。
(1)实体类
准备一个与表对应的实体类Department,先看表结构:
再根据各个字段来写实体类属性,创建一个pojo包,包下创建实体类Department,如下:
public class Department {
private String id;// 部门ID
private String name;// 部门名称
private String code;// 部门编号
private Date newDate;// 部门创建日期
private String descs;// 说明
// 构造器
public Department(String id, String name, String code, Date newDate, String descs) {
this.id = id;
this.name = name;
this.code = code;
this.newDate = newDate;
this.descs = descs;
}
public Department() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Date getNewDate() {
return newDate;
}
public void setNewDate(Date newDate) {
this.newDate = newDate;
}
public String getDescs() {
return descs;
}
public void setDescs(String descs) {
this.descs = descs;
}
}
(2)数据访问层Dao
创建一个dao包,包下新建一个DepDao类,如下:
/*
* 数据访问层
*/
public class DepDao {
// 定义一个方法,添加部门表记录
public void addDepartment(Department department) {
int res = 0;
// sql语句,?为占位符,后面会用参数替换
String sql = "insert into department(ID,NAME,CODE,NEWDATE,DESCS) values(?,?,?,?,?)";
// 获取Connection连接
Connection connection = DataSourceManager.getConnection();
PreparedStatement pStatement = null;
try {
// 从Connection连接中获取prepareStatement对象
pStatement = connection.prepareStatement(sql);
// 参数替换占位符
pStatement.setString(1, department.getId());
pStatement.setString(2, department.getName());
pStatement.setString(3, department.getCode());
// 注意,日期要传sql.Date类型
pStatement.setDate(4, new Date(department.getNewDate().getTime()));
pStatement.setString(5, department.getDescs());
// 执行更新
res = pStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭连接及接口对象
DataSourceManager.closeConnection(connection);
DataSourceManager.closeStatement(pStatement);
}
if (res == 1) {
System.out.println("已成功向Department表中添加一条数据!");
}
}
}
(3)测试
public static void main(String[] args) {
// 测试添加
Department dep = new Department("DEPID1009", "测试添加", "CS1001", new Date(), "测试测试!");
DepDao depDao = new DepDao();
depDao.addDepartment(dep);
}
执行,控制台:
查看Department表:
记录添加成功。
现在想修改刚才添加的那条新数据。在DepDao中添加以下方法:
// 定义一个方法,修改部门表记录
public void updateDepartment(Department department) {
int res = 0;
// sql语句
String sql = "update department set NAME=?,DESCS=? where ID=?";
// 获取连接
Connection connection = DataSourceManager.getConnection();
PreparedStatement ps = null;
try {
// 获取PreparedStatement对象
ps = connection.prepareStatement(sql);
// 占位符替换
ps.setString(1, department.getName());
ps.setString(2, department.getDescs());
ps.setString(3, department.getId());
// 执行更新
res = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭
DataSourceManager.closeConnection(connection);
DataSourceManager.closeStatement(ps);
}
if (res == 1) {
System.out.println("成功更新了Department表中ID为" + department.getId() + "的部门记录!");
}
}
测试:
public static void main(String[] args) {
// 测试修改
Department dep = new Department();
dep.setId("DEPID1009");
dep.setName("测试修改");
dep.setDescs("修改修改!");
DepDao depDao = new DepDao();
depDao.updateDepartment(dep);
}
现在想删除刚才修改的那条数据。在DepDao中添加以下方法:
// 定义一个方法,删除部门表记录
public void deleteDepartment(String id) {
int res = 0;
// sql语句
String sql = "delete from department where ID=?";
// 获取连接
Connection connection = DataSourceManager.getConnection();
PreparedStatement ps = null;
try {
// 获取PreparedStatement对象
ps = connection.prepareStatement(sql);
// 替换占位符
ps.setString(1, id);
// 执行更新
res = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭
DataSourceManager.closeConnection(connection);
DataSourceManager.closeStatement(ps);
}
if (res == 1) {
System.out.println("已成功删除Department表中ID为" + id + "的部门记录!");
}
}
测试:
public static void main(String[] args) {
// 测试删除
String id = "DEPID1009";
DepDao depDao = new DepDao();
depDao.deleteDepartment(id);
}
执行,控制台:
查看Department表:
DEPID1009的记录已经删除了。
这个接口类似于一个临时表,用来暂时存放数据库查询操作的所获得的结果集。它是JDBC API中唯一用来封装查询结果记录行的组件,ResultSet接口唯一创建方式是通过执行SQL查询返回创建此对象。ResultSet实例具有指向当前数据行的指针,指针的开始位置在第1条记录的前面,通过next()方法可将指针向下移动。api文档说明如下:
提供的方法如下:
由于方法比较多,就不进行说明了,标记的是用的较多的,其实用的最多的还是getXXX方法和next()方法,可以自行翻阅API文档查看。
先查询Department表,这张表中只有8条记录。在DepDao中添加以下方法:
// 定义一个方法,插叙部门表的所有记录
public List<Department> getAllDepartments() {
// 容器
List<Department> departments = new ArrayList<>();
// sql语句
String sql = "select ID,NAME,CODE,NEWDATE,DESCS from department";
// 获取连接
Connection connection = DataSourceManager.getConnection();
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
// 获取PreparedStatement对象
ps = connection.prepareStatement(sql);
// 执行查询,获取结果集
resultSet = ps.executeQuery();
// 结果集中有记录时,指针下移遍历结果集
while (resultSet.next()) {
Department department = new Department();
department.setId(resultSet.getString("ID"));
department.setName(resultSet.getString("NAME"));
department.setCode(resultSet.getString("CODE"));
department.setNewDate(resultSet.getDate("NEWDATE"));
department.setDescs(resultSet.getString("DESCS"));
// 封装各条记录,加到容器中
departments.add(department);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭
DataSourceManager.closeConnection(connection);
DataSourceManager.closeStatement(ps);
DataSourceManager.closeResultSet(resultSet);
}
return departments;
}
测试:
public static void main(String[] args) {
// 测试查询
DepDao depDao = new DepDao();
List<Department> departments = depDao.getAllDepartments();
System.out.println("一共有" + departments.size() + "个部门,信息如下:");
for (Department d : departments) {
System.out.println(d.getId() + "\t" + d.getName() + "\t" + d.getNewDate());
}
}
现在想查询Employee部门表中的数据:
一共有21条记录。
(1)实体类
查看表结构,先创建与表对应的实体类,在pojo包下创建Employee类,如下:
public class Employee {
private String id;//员工ID
private String name;//员工姓名
private Integer gender;//员工性别
private Date birth;//员工生日
private String address;//住址
private String phone;//员工联系方式
private Date entryDate;//员工进入部门的时间
private String email;//员工邮箱
private String depId;//员工所在部门编号
private String descs;//说明
//构造方法
public Employee(String id,String name,Integer gender,Date birth,String address,String phone,
Date entryDate,String email,String depId,String descs) {
this.id = id;
this.name = name;
this.gender = gender;
this.birth = birth;
this.address = address;
this.phone = phone;
this.entryDate = entryDate;
this.email = email;
this.depId = depId;
this.descs = descs;
}
public Employee() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Date getEntryDate() {
return entryDate;
}
public void setEntryDate(Date entryDate) {
this.entryDate = entryDate;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getDepId() {
return depId;
}
public void setDepId(String depId) {
this.depId = depId;
}
public String getDescs() {
return descs;
}
public void setDescs(String descs) {
this.descs = descs;
}
}
(2)数据访问层
在dao包下创建EmpDao类,如下:
/*
* 数据访问层
*/
public class EmpDao {
// 定义一个方法,查询员工表的所有记录
public List<Employee> getAllEmployees() {
// 容器
List<Employee> employees = new ArrayList<>();
// sql语句
String sql = "select ID,NAME,GENDER,BIRTH,ADDRESS,PHONE,ENTRYDATE,EMAIL,DEPID,DESCS from employee";
// 获取连接
Connection connection = DataSourceManager.getConnection();
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
// 获取PreparedStatement对象
ps = connection.prepareStatement(sql);
// 执行查询,获取ResultSet对象
resultSet = ps.executeQuery();
while (resultSet.next()) {
Employee employee = new Employee();
employee.setId(resultSet.getString("ID"));
employee.setName(resultSet.getString("NAME"));
employee.setGender(resultSet.getInt("GENDER"));
employee.setBirth(resultSet.getDate("BIRTH"));
employee.setAddress(resultSet.getString("ADDRESS"));
employee.setPhone(resultSet.getString("PHONE"));
employee.setEntryDate(resultSet.getDate("ENTRYDATE"));
employee.setEmail(resultSet.getString("EMAIL"));
employee.setDepId(resultSet.getString("DEPID"));
employee.setDescs(resultSet.getString("DESCS"));
// 封装结果,加到容器中
employees.add(employee);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭
DataSourceManager.closeConnection(connection);
DataSourceManager.closeStatement(ps);
DataSourceManager.closeResultSet(resultSet);
}
return employees;
}
}
测试:
public static void main(String[] args) {
// 测试查询
EmpDao empDao = new EmpDao();
List<Employee> employees = empDao.getAllEmployees();
System.out.println("一共有" + employees.size() + "名员工,信息如下:");
for (Employee emp : employees) {
String sex = emp.getGender() == 1 ? "男" : "女";
System.out.println(emp.getId() + "\t" + emp.getName() + "\t"
+ sex + "\t" + emp.getAddress() + "\t" + emp.getBirth());
}
}
只需要添加分页参数就行了,不过前提是要知道数据库支持的分页语句,MySQL和Oracle数据库的分页语句是不同的。这里就用简单一点的分页语句:
select * from 表 rownum<= a
minus
select * from 表 rownum<= b;
控制a、b的值就能实现分页。
在EmpDao中添加以下方法:
// 定义一个方法,实现分页查询
public Map<String, Object> queryEmpsPaged(int page, int size) {
// 容器
Map<String, Object> map = new HashMap<>();
int total = 0;// 总记录条数
int pageNum = 0;// 总页数
// sql,查询总记录条数
String sql0 = "select count(ID) from employee";
// sql,分页查询
String sql2 = "select ID,NAME,GENDER,BIRTH,ADDRESS from employee where rownum<=? minus "
+ "select ID,NAME,GENDER,BIRTH,ADDRESS from employee where rownum<=?";
// 获取连接
Connection connection = DataSourceManager.getConnection();
PreparedStatement ps0 = null;
ResultSet resultSet0 = null;
PreparedStatement ps2 = null;
ResultSet resultSet2 = null;
try {
// 获取PreparedStatement对象
ps0 = connection.prepareStatement(sql0);
resultSet0 = ps0.executeQuery();
while (resultSet0.next()) {
total = resultSet0.getInt(1);
}
// 总页数
pageNum = getPages(total, size);
// 存进map
map.put("total", total);
map.put("pageNum", pageNum);
ps2 = connection.prepareStatement(sql2);
// 替换占位符
ps2.setInt(1, page * size);
ps2.setInt(2, (page - 1) * size);
resultSet2 = ps2.executeQuery();
// 容器
List<Employee> list = new ArrayList<>();
while (resultSet2.next()) {
Employee emp = new Employee();
emp.setId(resultSet2.getString("ID"));
emp.setName(resultSet2.getString("NAME"));
emp.setGender(resultSet2.getInt("GENDER"));
emp.setAddress(resultSet2.getString("ADDRESS"));
emp.setBirth(resultSet2.getDate("BIRTH"));
list.add(emp);
}
// 存进map
map.put("empList", list);
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭
DataSourceManager.closeConnection(connection);
DataSourceManager.closeStatement(ps0);
DataSourceManager.closeStatement(ps2);
DataSourceManager.closeResultSet(resultSet0);
DataSourceManager.closeResultSet(resultSet2);
}
return map;
}
// 定义私有方法,统计页数
private int getPages(int totalSize, int size) {
return totalSize % size == 0 ? totalSize / size : (totalSize / size) + 1;
}
测试:
public static void main(String[] args) {
// 测试分页查询
EmpDao empDao = new EmpDao();
Scanner in = new Scanner(System.in);
System.out.println("请输入页码数:");
int page = in.nextInt();
System.out.println("请输入每页记录条数:");
int size = in.nextInt();
Map<String,Object> map = empDao.queryEmpsPaged(page, size);
//获取总记录条数和页数
int total = (Integer) map.get("total");
int pageSum = (Integer) map.get("pageNum");
System.out.println("一共" + total + "条记录,一共" + pageSum + "页!");
System.out.println("-----------------------------------------");
System.out.println("当前第" + page + "页,总共" + pageSum + "页!");
System.out.println("-----------------------------------------");
List<Employee> employees = (List<Employee>) map.get("empList");
for(Employee emp:employees) {
String sex = emp.getGender() == 0 ? "女" : "男";
System.out.println(emp.getId() + "\t" + emp.getName() + "\t"
+ sex + "\t" + emp.getBirth() + "\t" + emp.getAddress());
}
}
模糊查询只要传入参数就行了。在EmpDao中添加以下方法:
// 定义一个方法,按照姓名和住址进行模糊查询
public List<Employee> queryByConditions(String name, String address) {
// 容器
List<Employee> employees = new ArrayList<>();
// sql语句
String sql = "select ID,NAME,GENDER,BIRTH,ADDRESS from employee where name like '" + name + "%'"
+ " or address like '%" + address + "'";
// 获取连接
Connection connection = DataSourceManager.getConnection();
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
ps = connection.prepareStatement(sql);
resultSet = ps.executeQuery();
while (resultSet.next()) {
Employee emp = new Employee();
emp.setId(resultSet.getString("ID"));
emp.setName(resultSet.getString("NAME"));
emp.setGender(resultSet.getInt("GENDER"));
emp.setBirth(resultSet.getDate("BIRTH"));
emp.setAddress(resultSet.getString("ADDRESS"));
employees.add(emp);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭
DataSourceManager.closeConnection(connection);
DataSourceManager.closeStatement(ps);
DataSourceManager.closeResultSet(resultSet);
}
return employees;
}
测试:
public static void main(String[] args) {
// 测试模糊查询
EmpDao empDao = new EmpDao();
//查找姓黄或者住址在某道观的记录
List<Employee> employees = empDao.queryByConditions("黄", "道观");
for(Employee emp:employees) {
String sex = emp.getGender() == 0 ? "女" : "男";
System.out.println(emp.getId() + "\t" + emp.getName() + "\t"
+ sex + "\t" + emp.getBirth() + "\t" + emp.getAddress());
}
}
因为查询的是多表,所以不能用List集合,因为List集合泛型规定了数据类型,很明显这查的是多表中的数据,并不是一种类型,可以考虑使用Map进行封装。
在dao包下创建DepEmpDao,如下:
/*
* 数据访问层
*/
public class DepEmpDao {
// 定义方法,进行表连接查询
public List<Map<String, Object>> getDepEmp() {
// 容器
List<Map<String, Object>> list = new ArrayList<>();
// sql语句
String sql = "select dep.NAME dName,emp.Name eName,emp.gender eGender,emp.address eAddress"
+ " from department dep inner join employee emp on dep.ID = emp.DEPID order by dep.NAME";
Connection connection = DataSourceManager.getConnection();
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
ps = connection.prepareStatement(sql);
resultSet = ps.executeQuery();
while (resultSet.next()) {
// 容器
Map<String, Object> map = new HashMap<>();
map.put("dName", resultSet.getString("dNAME"));
map.put("eName", resultSet.getString("eName"));
map.put("eGender", resultSet.getInt("eGender"));
map.put("eAddress", resultSet.getString("eAddress"));
list.add(map);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭
DataSourceManager.closeConnection(connection);
DataSourceManager.closeStatement(ps);
DataSourceManager.closeResultSet(resultSet);
}
return list;
}
}
测试:
public static void main(String[] args) {
// 测试表连接查询
DepEmpDao deDao = new DepEmpDao();
//查找姓黄或者住址在某道观的记录
List<Map<String,Object>> list = deDao.getDepEmp();
System.out.println("一共" + list.size() + "条记录!");
System.out.println("----------------------------------");
for(Map<String,Object> map:list) {
String dName = (String) map.get("dName");
String eName = (String) map.get("eName");
String sex = map.get("eGender").toString().equals("0") ? "女" : "男";
String address = (String) map.get("eAddress");
System.out.println(dName + "\t" + eName + "\t"
+ sex + "\t" + address);
}
}
想在JDBC中调用整个函数。
函数是有返回值的,调用函数使用PreparedStatement接口即可,就普通的SQL语句。
dao包下创建类FunctionDao,如下:
/*
* 数据访问层
*/
public class FunctionDao {
// 测试调用函数Function
public int useFunction(int a, int b) {
int res = 0;
// sql语句
String sql = "select fun_demo2(?,?) from dual";
// 获取连接
Connection connection = DataSourceManager.getConnection();
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
ps = connection.prepareStatement(sql);
//参数替换占位符
ps.setInt(1, a);
ps.setInt(2, b);
// 执行
resultSet = ps.executeQuery();
while (resultSet.next()) {
//获取返回值
res = resultSet.getInt(1);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭
DataSourceManager.closeConnection(connection);
DataSourceManager.closeStatement(ps);
DataSourceManager.closeResultSet(resultSet);
}
return res;
}
}
测试:
public static void main(String[] args) {
// 测试调用函数
FunctionDao fDao = new FunctionDao();
int res = fDao.useFunction(25, 4);
System.out.println("函数的返回结果是:" + res);
}
这个接口是和存储过程有关的,它是PreparedStatement接口的子接口。api文档说明如下:
提供的方法如下:
用的比较多的方法是getXXX和registerOutParameter这两个方法,其余参考API文档。
现在想调用这个存储过程。
调用存储过程需要用到CallableStatement接口。下面进行测试。
在dao包下创建类ProcudureDao类,如下:
/*
* 数据访问层
*/
public class ProcedureDao {
// 测试调用存储过程
public float useProcedure(float up, float down, float h, float area) {
// sql
String sql = "{call pro_demo1(?,?,?,?)}";
// 获取连接
Connection connection = DataSourceManager.getConnection();
CallableStatement cs = null;
try {
// 获取CallableStatement对象
// CallableStatement是PreparedStatement的子接口,是处理存储过程的唯一组件
cs = connection.prepareCall(sql);
// 参数替换占位符
cs.setFloat(1, up);
cs.setFloat(2, down);
cs.setFloat(3, h);
// 注册输出参数类型
cs.registerOutParameter(4, Types.FLOAT);
// 执行
cs.execute();
// 获取输出参数值
area = cs.getFloat(4);
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭
try {
connection.close();
cs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return area;
}
}
测试:
public static void main(String[] args) {
// 测试调用存储过程
ProcedureDao pDao = new ProcedureDao();
float res = pDao.useProcedure(2.5f, 7.5f, 4.0f, 0.0f);
System.out.println("梯形的面积是:" + res);
}
批处理(Batch proces)是指一次连接访问数据中发送一组SQL操作语句,通常对数据库多条数据行进行更新操作。合理使用批处理能够在最少次访问数据库时执行多条SQL操作从而提高数据库的访问速度并提高数据库的应用效率。一般情况下批处理执行类似的相近操作,如批量修改、批量处理、删除插入等。
现在想往这张表中一次性插入10条记录:
那么会用到批处理。下面使用JDBC写一个进行批处理的例子。
(1)实体类
先看表结构:
然后设计实体类。在pojo包下创建类Person,如下:
public class Person {
private String name;// 姓名
private int age;// 年龄
private float height;// 身高
private double money;// 工资
// 构造器
public Person(String name, int age, float height, double money) {
this.name = name;
this.age = age;
this.height = height;
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
(2)数据访问层
在dao包下创建类PersonDao,如下:
/*
* 数据访问层
*/
public class PersonDao {
// 测试批处理插入记录
public void insertButch(List<Person> persons) {
// sql语句
String sql = "insert into person(NAME,AGE,HEIGHT,MONEY) values (?,?,?,?)";
// 获取连接
Connection connection = DataSourceManager.getConnection();
PreparedStatement ps = null;
try {
ps = connection.prepareStatement(sql);
for (Person p : persons) {
// 占位符替换
ps.setString(1, p.getName());
ps.setInt(2, p.getAge());
ps.setFloat(3, p.getHeight());
ps.setDouble(4, p.getMoney());
// 添加到批处理
ps.addBatch();
}
// 执行批处理,返回int型数组
int[] res = ps.executeBatch();
System.out.println("成功向Person表中添加" + res.length + "条记录!");
} catch (SQLException e) {
e.printStackTrace();
} finally {
DataSourceManager.closeConnection(connection);
DataSourceManager.closeStatement(ps);
}
}
}
(3)测试
public static void main(String[] args) {
// 测试批处理
PersonDao pDao = new PersonDao();
List<Person> list = new ArrayList<>();
Person p0 = new Person("程序员A", 25, 178.5f, 50000.2);
Person p2 = new Person("程序员B", 25, 178.5f, 50000.2);
Person p3 = new Person("程序员C", 25, 178.5f, 50000.2);
Person p4 = new Person("程序员D", 25, 178.5f, 50000.2);
Person p5 = new Person("程序员E", 25, 178.5f, 50000.2);
Person p6 = new Person("程序员F", 25, 178.5f, 50000.2);
Person p7 = new Person("程序员G", 25, 178.5f, 50000.2);
Person p8 = new Person("程序员H", 25, 178.5f, 50000.2);
Person p9 = new Person("程序员I", 25, 178.5f, 50000.2);
Person p10 = new Person("程序员J", 25, 178.5f, 50000.2);
list.add(p0);
list.add(p2);
list.add(p3);
list.add(p4);
list.add(p5);
list.add(p6);
list.add(p7);
list.add(p8);
list.add(p9);
list.add(p10);
pDao.insertButch(list);
}
执行,控制台:
查看Person表:
添加成功,那么批处理成功了。
事务的四大特性:
对于默认的JDBC操作,事务的执行是自动开启的,每个SQL执行都将在不产生错误时自动提交,如果在一个线程方法中涉及多个相关SQL语句操作,则应在程序中设计事务的手动处理,要么全部成功提交事务(commit),要么出现操作异常回滚(rollback)所有操作。
事务处理的相关方法:
事务的隔离级别
事务的隔离级别有5种,如下:
专有名词说明
这是以前写的转账事务:
-- 银行转账业务
declare
success string(64):='转账成功!';
fail string(64):='转账失败!';
-- 定义异常变量
error_fail exception;
-- 异常变量初始化
pragma exception_init(error_fail,-2290);
begin
-- 转入记录
insert into business values(trig_seq.nextval,10000,1,to_char(systimestamp,'yyyy-MM-dd HH24:mi:ss'),trig_seq.nextval);
-- 转出记录
insert into business values(trig_seq.nextval,10000,0,to_char(systimestamp,'yyyy-MM-dd HH24:mi:ss'),trig_seq.nextval);
--更新账户余额表
--转入方账户加钱
update accounts set balance=balance+10000 where id=102;
--转出方账户扣钱
update accounts set balance=balance-10000 where id=103;
-- 事务提交
commit;
-- 转账成功时
dbms_output.put_line(success);
-- 失败时
exception when error_fail then
dbms_output.put_line(fail);
-- 事务回滚
rollback;
end;
在dao包下创建TransactionDao类,如下:
/*
* 数据处理层
*/
public class TransactionDao {
// 模拟银行转账事务
public void jdbcTransaction(double money, int inId, int outId) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = sdf.format(new Date());
// sql语句
String sql0 = "insert into business values(trig_seq.nextval,?,1,?,?)";
String sql2 = "insert into business values(trig_seq.nextval,?,0,?,?)";
String sql3 = "update accounts set balance = balance + ? where id = ?";
String sql4 = "update accounts set balance = balance - ? where id = ?";
// 获取连接
Connection connection = DataSourceManager.getConnection();
PreparedStatement ps0 = null;
PreparedStatement ps2 = null;
PreparedStatement ps3 = null;
PreparedStatement ps4 = null;
try {
// 关闭事务的自动提交
connection.setAutoCommit(false);
ps0 = connection.prepareStatement(sql0);
// 替换参数
ps0.setDouble(1, money);
ps0.setString(2, dateStr);
ps0.setInt(3, inId);
ps2 = connection.prepareStatement(sql2);
// 替换参数
ps2.setDouble(1, money);
ps2.setString(2, dateStr);
ps2.setInt(3, outId);
ps3 = connection.prepareStatement(sql3);
ps3.setDouble(1, money);
ps3.setInt(2, inId);
ps4 = connection.prepareStatement(sql4);
ps4.setDouble(1, money);
ps4.setInt(2, outId);
// 执行
ps0.executeUpdate();
ps2.executeUpdate();
ps3.executeUpdate();
ps4.executeUpdate();
// 事务手动提交
connection.commit();
System.out.println("转账成功!");
} catch (SQLException e) {
e.printStackTrace();
// 发生异常说明失败
System.err.println("转账失败!");
// 事务进行回滚
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
// 关闭
DataSourceManager.closeConnection(connection);
DataSourceManager.closeStatement(ps0);
DataSourceManager.closeStatement(ps2);
DataSourceManager.closeStatement(ps3);
DataSourceManager.closeStatement(ps4);
}
}
}
测试:
public static void main(String[] args) {
// 测试转账事务
TransactionDao tDao = new TransactionDao();
tDao.jdbcTransaction(10000, 1000, 999);
}
执行,控制台:
查看表:
转账成功,交易表中添加了两条记录,余额表中用户的余额更新了。
现在ID为999的余额为0,无法进行转账,再执行一次:
因为余额的约束是不小于0,这里直接报错,再看余额表:
没变化,说明事务回滚成功。
其实前面用到了c3p0的连接池,但是在代码里我并没有使用。如下:
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。
在程序初始化的时候创建一定数量的数据库连接,用完可以放回去,下一个在接着用,通过配置连接池的参数来控制连接池中的初始连接数、最小连接、最大连接、最大空闲时间这些参数保证访问数据库的数量在一定可控制的范围类,防止系统崩溃。
数据库连接是一种关键、有限的、昂贵的资源,创建和释放数据库连接是一个很耗时的操作,频繁地进行这样的操作将占用大量的性能开销,进而导致网站的响应速度下降,严重的时候可能导致服务器崩溃;数据库连接池可以节省系统许多开销。
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数制约。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
对于最小连接数和最大连接数的设置应考虑以下几点: