JDBC:
Java Data Base Connectivity (java数据库连接)
如纽带一样连接着 Java 应用程序和数据库。
可以为多种数据库提供统一的访问
体现了Java“编写一次,处处运行”的伟大精神。
步骤:
例:
package com.imooc.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class DBUtil {
private static final String URL="jdbc:mysql://127.0.0.1:3306/imooc";
private static final String USER="root";
private static final String PASSWORD="root";
public static void main(String[] args) throws Exception {
// 加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
// 获得数据库的连接
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
// 通过数据库的连接操作数据库,实现增删改查
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT user_name,age FROM imooc_goddess"); // 将查询的数据存放在这个对象里面
// 如果对象里面有数据(rs.next返回true或false),就打印
while(rs.next()) {
System.out.println(rs.getString("user_name") + "," + rs.getString("age"));
}
}
}
样设:
写顺序:从下往上。
搭建模型层:
静态块: static {} 在类加载的时候就执行,且整个应用程序生命中只执行一次。
public class DBUtil {
private static final String URL = "jdbc:mysql://127.0.0.1:3306/imooc";
private static final String USER = "root";
private static final String PASSWORD = "root";
private static Connection conn = null;
static {
try {
// 加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
// 获得数据库的连接
conn = DriverManager.getConnection(URL, USER, PASSWORD);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
return conn;
}
}
要有数据库对应的实体类
public class Goddess {
private Integer id;
private String user_name;
private Integer sex;
private Integer age;
private Date birthday;
private String email;
private String mobile;
private String create_user;
private String update_user;
private Date create_date;
private Date update_date;
private Integer isbel;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUser_name() {
return user_name;
}
public void setUser_name(String user_name) {
this.user_name = user_name;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getCreate_user() {
return create_user;
}
public void setCreate_user(String create_user) {
this.create_user = create_user;
}
public String getUpdate_user() {
return update_user;
}
public void setUpdate_user(String update_user) {
this.update_user = update_user;
}
public Date getCreate_date() {
return create_date;
}
public void setCreate_date(Date create_date) {
this.create_date = create_date;
}
public Date getUpdate_date() {
return update_date;
}
public void setUpdate_date(Date update_date) {
this.update_date = update_date;
}
public Integer getIsbel() {
return isbel;
}
public void setIsbel(Integer isbel) {
this.isbel = isbel;
}
}
且有各个功能的实现的类:
查找:
public List<Goddess> query() throws Exception {
Connection conn = DBUtil.getConnection();
// 通过数据库的连接操作数据库,实现增删改查
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT user_name,age FROM imooc_goddess"); // 将查询的数据存放在这个对象里面
List<Goddess> gs = new ArrayList<Goddess>();
Goddess g = null;
// 如果对象里面有数据(rs.next返回true或false)
while(rs.next()) {
g = new Goddess();
g.setUser_name(rs.getString("user_name"));
g.setAge(rs.getInt("age"));
gs.add(g);
}
return gs;
}
public Goddess get() {
return null;
}
}
添加:
public void addGoddess(Goddess g) throws SQLException {
Connection conn = DBUtil.getConnection();
String sql = "" + "INSERT INTO imooc_goddess" +
"(user_name,sex,age,birthday,email,mobile," +
"create_user,create_date,update_user,update_date,isdel)" +
"values(" +
"?,?,?,?,?,?,?,current_date(),?,current_date(),?)";
// 预编译
PreparedStatement ptmt = conn.prepareStatement(sql);
// 传参给预编译符 "?" 去赋值
ptmt.setString(1, g.getUser_name());
ptmt.setInt(2, g.getSex());
ptmt.setInt(3, g.getAge());
ptmt.setDate(4, new Date(g.getBirthday().getTime()));
ptmt.setString(5, g.getEmail());
ptmt.setString(6, g.getMobile());
ptmt.setString(7, g.getCreate_user());
ptmt.setString(8, g.getUpdate_user());
ptmt.setInt(9, g.getIsbel());
ptmt.execute();
}
current_date():获取当天日期(MYSQL语句)
删除:
public void delGoddess(Integer id) throws Exception {
Connection conn = DBUtil.getConnection();
String sql = "DELETE FROM" + " imooc_goddess" +
" WHERE" + " id = ?";
// 预编译
PreparedStatement ptmt = conn.prepareStatement(sql);
// 传参给预编译符 "?" 去赋值
ptmt.setInt(1, id);
ptmt.execute();
}
public Goddess get(Integer id) throws Exception {
Connection conn = DBUtil.getConnection();
// 通过数据库的连接操作数据库,实现增删改查
String sql = "SELECT * FROM" + " imooc_goddess" +
" WHERE" + " id = ?";
PreparedStatement ptmt = conn.prepareStatement(sql);
ptmt.setInt(1, id);
ResultSet rs = ptmt.executeQuery();
Goddess g = null;
if(rs.next()) {
g = new Goddess();
g.setId(rs.getInt("id"));
g.setUser_name(rs.getString("user_name"));
g.setAge(rs.getInt("age"));
g.setSex(rs.getInt("sex"));
g.setBirthday(rs.getDate("birthday"));
g.setEmail(rs.getString("email"));
g.setMobile(rs.getString("mobile"));
g.setCreate_date(rs.getDate("create_date"));
g.setCreate_user(rs.getString("create_user"));
g.setUpdate_date(rs.getDate("update_date"));
g.setUpdate_user(rs.getString("update_user"));
g.setIsbel(rs.getInt("isdel"));
}
return g;
}
查找的最后执行语句是:ptmt.executeQuery()
按特定语句段查询:
public List<Goddess> query(List<Map<String,Object>> params) throws Exception {
List<Goddess> result = new ArrayList<Goddess>();
Connection conn = DBUtil.getConnection();
// 通过数据库的连接操作数据库,实现增删改查
StringBuilder sb = new StringBuilder();
sb.append("SELECT * FROM imooc_goddess WHERE 1=1 ");
if(params!=null && params.size()>0) {
for(Map<String,Object> mm : params) {
sb.append(" and " + mm.get("name") + " " + mm.get("rela") + " " + mm.get("value") + " ");
}
}
PreparedStatement ptmt = conn.prepareStatement(sb.toString());
ResultSet rs = ptmt.executeQuery();
// 如果对象里面有数据(rs.next返回true或false)
while(rs.next()) {
Goddess g = new Goddess();
g.setId(rs.getInt("id"));
g.setUser_name(rs.getString("user_name"));
g.setAge(rs.getInt("age"));
g.setSex(rs.getInt("sex"));
g.setBirthday(rs.getDate("birthday"));
g.setEmail(rs.getString("email"));
g.setMobile(rs.getString("mobile"));
g.setCreate_date(rs.getDate("create_date"));
g.setCreate_user(rs.getString("create_user"));
g.setUpdate_date(rs.getDate("update_date"));
g.setUpdate_user(rs.getString("update_user"));
g.setIsbel(rs.getInt("isdel"));
result.add(g);
}
return result;
}
主函数测试语句:
List<Map<String,Object>> params = new ArrayList<Map<String,Object>>();
Map<String,Object> param = new HashMap<String, Object>();
param.put("name", "user_name");
param.put("rela", "=");
param.put("value", "'小美'");
param.put("name", "mobile");
param.put("rela", "=");
param.put("value", "'13915487681'");
params.add(param);
List<Goddess> result = g.query(params);
for(Goddess goddess : result) {
System.out.println(goddess);
}
小技巧:在 WHERE 后加 1=1 ,永为真,再加 and 就可以包括第一条语句在内的所有语句了
搭建控制层:
连接视图层和模型层的桥梁(通过调用模型层的方法实现)
public class GoddessAction {
private GoddessDao dao = new GoddessDao();
// 添加
public void add(Goddess goddess) throws Exception {
dao.addGoddess(goddess);
}
// 更新
public void edit(Goddess goddess) throws Exception {
dao.updateGoddess(goddess);
}
// 删除
public void del(Integer id) throws Exception {
dao.delGoddess(id);
}
// 全部查找
public List<Goddess> query() throws Exception {
return dao.query();
}
// 按条件查找
public List<Goddess> query(List<Map<String,Object>> params) throws Exception {
return dao.query(params);
}
// 获得某个ID的信息
public Goddess get(Integer id) throws Exception {
return dao.get(id);
}
}
搭建视图层:
public class View {
private static final String CONTEXT="欢迎来到女神禁区:\n" +
"下面是女神禁区的功能列表:\n" +
"[MAIN/M]:主菜单\n" +
"[QUERY/Q]:查看全部女神的信息\n" +
"[GET/G]:查看某位女神的详细信息\n" +
"[ADD/A]:添加女神信息\n" +
"[UPDATE/U]:更新女神信息\n" +
"[DELETE/D]:删除女神信息\n" +
"[SEARCH/S]:查询女神信息(根据姓名、手机号来查询)\n" +
"[EXIT/E]:退出女神禁区\n" +
"[BREAK/B]:退出当前功能,返回主菜单" ;
private static final String OPERATION_MAIN="MAIN";
private static final String OPERATION_QUERY="QUERY";
private static final String OPERATION_GET="GET";
private static final String OPERATION_ADD="ADD";
private static final String OPERATION_UPDATE="UPDATE";
private static final String OPERATION_DELETE="DELETE";
private static final String OPERATION_SEARCH="SEARCH";
private static final String OPERATION_EXIT="EXIT";
private static final String OPERATION_BREAK="BREAK";
public static void main(String[] args) {
System.out.println(CONTEXT);
Goddess goddess = new Goddess();
GoddessAction action = new GoddessAction();
String prenious = null;
Integer step = 1;
Scanner scan = new Scanner(System.in);
// 有输入值的时候,保持程序的一直运行
while(scan.hasNext()) {
String in = scan.next();
// 如果输入的是 EXIT , 或者 E , 退出
if(in.toUpperCase().equals(OPERATION_EXIT) || in.toUpperCase().equals(OPERATION_EXIT.substring(0, 1))) {
System.out.println("您已成功退出女神禁区。");
break;
} else if (in.toUpperCase().equals(OPERATION_QUERY) || in.toUpperCase().equals(OPERATION_QUERY.substring(0, 1))) {
try {
List<Goddess> list = action.query();
for (Goddess g : list) {
System.out.println(g.getId() + ",姓名:" + g.getUser_name());
}
} catch (Exception e) {
e.printStackTrace();
}
} else if (in.toUpperCase().equals(OPERATION_ADD) || in.toUpperCase().equals(OPERATION_ADD.substring(0, 1)) || prenious.equals(OPERATION_ADD)) {
prenious = OPERATION_ADD;
if(1 == step) {
System.out.println("请输入女神的 [姓名] ");
} else if (2 == step) {
goddess.setUser_name(in);
System.out.println("请输入女神的 [年龄] ");
} else if (3 == step) {
goddess.setAge(Integer.valueOf(in));
System.out.println("请输入女神的 [生日] , 格式如:yyyy-MM-dd");
} else if (4 == step) {
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
Date birthday = null;
try {
birthday = sf.parse(in);
goddess.setBirthday(birthday);
System.out.println("请输入女神的 [邮箱] ");
} catch (ParseException e) {
e.printStackTrace();
System.out.println("您输入的格式有误,请重新输入:");
step = 3;
}
} else if (5 == step) {
goddess.setEmail(in);
System.out.println("请输入女神的 [手机号] ");
} else if (6 == step) {
goddess.setMobile(in);
System.out.println("新增女神成功!");
try {
action.add(goddess);
} catch (Exception e) {
e.printStackTrace();
System.out.println("新增女神失败。");
}
}
if (OPERATION_ADD.equals(prenious)) {
step ++;
}
} else {
System.out.println("您输入的值为:" + in);
}
}
}
}
JDBC各种连接数据库方式的对比:
JDBC + ODBC桥的方式。
特点:需要数据库的 ODBC 驱动,仅适用于微软的系统。JDBC + 厂商API的形式。
特点:厂商API一般使用C编写。JDBC + 厂商Database Connection
Server + DataBase 的形式。
特点:在 Java 与 Database 之间架起了一台专门用与数据库连接的服务器(一般由数据库厂商提供)JDBC + DataBase 的连接方式。(推荐使用)
特点:这使得Application与数据库分开,开发者只需关心内部逻辑的实现而不需注重数据库连接的具体实现。
无参数存储过程的调用:
public static void select_nofilter() throws SQLException {
// 1.获得连接
Connection conn = DBUtil.getConnection();
// 2.获得 callablestatement
CallableStatement cs = conn.prepareCall("CALL sp_select_nofilter");
// 3.执行存储过程
cs.execute();
// 4.处理返回的结果:结果集或出参
ResultSet rs = cs.getResultSet();
while(rs.next()) {
System.out.println(rs.getString("user_name") + rs.getString("email") + rs.getString("mobile"));
}
}
带参数的存储过程:
BEGIN
IF sp_name IS NULL OR sp_name = "" THEN
SELECT * FROM imooc_goddess;
ELSE
IF length(sp_name)=11 AND substring(sp_name,1,1)=1 THEN
SELECT * FROM imooc_goddess WHERE mobile=sp_name;
ELSE
SELECT * FROM imooc_goddess WHERE user_name LIKE CONCAT('%',sp_name,'%');
END IF;
END IF;
END
传参:IN sp_name VARCHAR(20)
如果 sp_name 为空或等于 NULL ,则查询全表。
如果长度为 11 且开头字母为 1,以手机号匹配查询。
否则,以名称匹配。
(IF-THEN连用)
public static List<Goddess> select_filter(String sp_name) throws Exception {
// 1.获得连接
Connection conn = DBUtil.getConnection();
// 2.获得 callablestatement
CallableStatement cs = conn.prepareCall("CALL sp_select_filter(?)");
cs.setString(1, sp_name);
// 3.执行存储过程
cs.execute();
// 4.处理返回的结果:结果集或出参
ResultSet rs = cs.getResultSet();
Goddess g = null;
List<Goddess> result = new ArrayList<Goddess>();
while(rs.next()) {
g = new Goddess();
g.setId(rs.getInt("id"));
g.setUser_name(rs.getString("user_name"));
g.setAge(rs.getInt("age"));
g.setMobile(rs.getString("mobile"));
result.add(g);
}
return result;
}
调用带输出参数的存储过程:
BEGIN
SELECT count(*) INTO count FROM imooc_goddess;
END
将全表记录的数量(count)放入(INTO)count变量中
public static Integer select_count () throws Exception {
Integer count = 0;
// 1.获得连接
Connection conn = DBUtil.getConnection();
// 2.获得 callablestatement
CallableStatement cs = conn.prepareCall("CALL sp_select_count(?)");
cs.registerOutParameter(1, Types.INTEGER); // Types JDBC封装的常用的数据类型
// 3.执行存储过程
cs.execute();
count = cs.getInt(1);
return count;
}
CallableStatement 的 registerOutParameter 方法获得输出参数,TYPES 为JDBC常用封装类型。
不用 ResultSet 而直接用 cs.getInt(1) 获取第一个出参。
事务管理:
事务是作为单个逻辑工作单元执行的一系列操作。
这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行。
特点:
1.原子性
事务是一个完整的操作。
2.一致性
当事务完成时,数据必须处于一致状态。
(如这边减少了100,那边应该同时增加100)
3.隔离性
对数据进行修改的所有并发事务是彼此隔离的。
如:
A: z+100,x-100
B: z+100,x-100,
当A事务处理时,B等待(同一时间对数据库的操作保持一个事务的锁定)
4.永久性
事务完成后,它对数据库的修改被永久保存。
JDBC对事务管理的支持:
1.我们通过提交 commit() 或是回退 rollback() 来管理事务的操作。
(回滚:回到之前未更新删除等状态)
2.事务操作默认是自动提交。
3.可以通过调用 setAutoCommit(false) 来禁止自动提交。
无事务管理:
public String trans (Account from, Account to, Double amount) throws Exception {
AccountDao accountDao = new AccountDao();
TransInfoDao transInfoDao = new TransInfoDao();
from.setAmount(from.getAmount()-amount);
accountDao.updateAccount(from);
String s = null;
s.split("1");
to.setAmount(to.getAmount()+amount);
accountDao.updateAccount(to);
TransInfo info = new TransInfo();
info.setSource_account(from.getAccount());
info.setSource_id(from.getId());
info.setDestination_account(to.getAccount());
info.setDestination_id(to.getId());
info.setAmount(amount);
transInfoDao.addTransInfo(info);
return "sccess";
}
中间的两行 String 和 split 会报空指针错误,如果没有事务管理,数据库自动提交将导致 a 的钱没了,但是 maket 钱也没增加(因为中间报错就停了)
有事务管理:
public String transaction (Account from, Account to, Double amount) throws Exception {
Connection conn = DBUtil.getConnection();
conn.setAutoCommit(false);
try {
AccountDao accountDao = new AccountDao();
TransInfoDao transInfoDao = new TransInfoDao();
from.setAmount(from.getAmount()-amount);
accountDao.updateAccount(from);
/*String s = null;
s.split("1");*/
to.setAmount(to.getAmount()+amount);
accountDao.updateAccount(to);
TransInfo info = new TransInfo();
info.setSource_account(from.getAccount());
info.setSource_id(from.getId());
info.setDestination_account(to.getAccount());
info.setDestination_id(to.getId());
info.setAmount(amount);
transInfoDao.addTransInfo(info);
conn.commit();
return "success";
} catch (Exception e) {
conn.rollback();
e.printStackTrace();
return "fail";
}
}
获取连接,加了 conn.setAutoCommit(false) 将自动提交关闭。
用 try-catch 去捕获可能出现的错误,如果出错,就调用 conn.rollback() 回滚。
如果没错,再用 conn.commit() 提交。
数据库连接池:
常用的开源的连接池:
1.dbcp
2.c3p0
dbcp:
1.导入相关jar包
commons-dbcp2-2.1.1.jar
commons-pool2-2.4.2.jar
commons-logging-1.2.jar
2.在项目根目录增加配置文件
dbcp.properties
可以手动将所有东西写进去:
也可以直接引入配置文件:
public class DBCPUtil {
private static DataSource DS;
private static final String configFile = "/dbcp.properties";
public DBCPUtil () {
initDbcp();
}
private static void initDbcp () {
Properties pops = new Properties();
try {
pops.load(Object.class.getResourceAsStream(configFile));
DS = BasicDataSourceFactory.createDataSource(pops);
} catch (Exception e) {
e.printStackTrace();
}
}
public Connection getConn () {
Connection con = null;
if (DS != null) {
try {
con = DS.getConnection();
} catch (SQLException e) {
e.printStackTrace(System.err);
}
try {
con.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
return con;
}
}
两个变量: DataSource DS 和 String configFile=”/dbcp.properties”
在构造方法中调用初始化 initDbcp()
初始化方法中声明配置变量 Properties pops,加载配置文件 pops.load(Object.class.getResourceAsStream(configFile))
用 DS 接收 DS = BasicDataSourceFactory.createDataSource(pops)
获得连接方法:
如果 DS 非空
con = DS.getConnection();
设置不自动提交
com.setAutoCommit(false);
return con
配置文件:
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc\:mysql\://127.0.0.1\:3306/imooc_db?useUnicode\=true&characterEncoding=UTF-8
username=root
password=root
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=30
#<!-- 最大空闲连接 -->
maxIdle=10
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=1000
initialSize=5
minIdle=1
removeAbandoned=true
removeAbandonedTimeout=180
在Dao层用DBCP方式获取连接:
DBCPUtil db = new DBCPUtil();
Connection conn = db.getConn();
C3P0:
C3P0是一个开源的JDBC的连接池,它实现了数据源和JNDI绑定,支持JDBC和JDBC2的标准扩展。
1.导入相关jar包
c3p0-0.9.2-pre4.jar
mchange-commons-java-0.2.2.jar
2.在项目根目录增加配置文件
c3p0.properties
3.编写类文件,创建连接池
配置文件:
c3p0.driverClass=com.mysql.jdbc.Driver
c3p0.jdbcUrl=jdbc:\:mysql\://127.0.0.1\:3306/imooc_db?useUnicode\=true&characterEncoding=UTF-8
c3p0.user=root
c3p0.password=root
工程文件:
public class C3P0Util {
private static ComboPooledDataSource ds = new ComboPooledDataSource();
public static Connection getConnection () {
try {
return ds.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
c3p0和dbcp的不同点:
JDBC升级替代品—Commons-dbutils:
Apache 组织提供的一个开源JDBC工具类库,对传统操作数据库的类进行二次封装,可以把结果集转化成List
特点:
1.杜绝资源泄露。修正JDBC代码并不困难,但是这通常导致连接泄露并且难以跟踪到。
2.大段的持久化数据到数据库代码彻底精简,剩下的代码清晰地表达了编码的意图。
3.不需要手工从ResultSet里set值到JavaBean中,每一行数据都将会以一个Bean实例的形式出现。
核心接口:
1.DbUtils
提供如关闭连接、装载JDBC驱动程序等常规工作的工具类
2.QueryRunner
该类简单化了SQL查询,它常与ResultSetHandler组合在一起使用
3.ResultSetHandler
执行处理一个java.sql.ResultSet,将数据转变并处理为任何一种形式,这样有益于其应用而且使用起来更容易
示例:
Hibernate:
一种Java语言下的对象关系映射解决方案。它是一种自由、开源的软件。
优点:
1.轻量级的ORM框架
2.对JDBC进行了很好的封装,使用了ORM做了映射,那么就可以通过面向对象的方式很容易的操作数据库了
3.它还提供了缓存机制,可以提高效率
缺点:
如果对大量数据进行频繁的操作,性能效率比较低,不如直接使用JDBC
核心接口:
myBatis:
MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。
特点:
1.易于上手和掌握
2.sql写在xml里,便于统一管理和优化
3.解除sql与程序代码的耦合
4.提供映射标签,支持对象与数据库的orm字段关系映射
5.提供对象关系映射标签,支持对象关系组建维护
6.提供xml标签,支持编写动态sql
JDBC理解:
1.Class.forName()将对应的驱动类加载到内存中,然后执行内存中的static静态代码段,代码段中,会创建一个驱动Driver的实例,放入DriverManager中,供DriverManager使用。
2.DriverManager:管理Driver驱动
DriverManger可以注册和删除加载的驱动程序
可以根据给定的url获取符合url协议的驱动Driver或者是建立Conenction连接,进行数据库交互。
3.java.sql.Driver接口规定了厂商实现该接口,并且定义自己的URL协议。厂商们实现的Driver接口通过acceptsURL(String url)来判断此url是否符合自己的协议,如果符合自己的协议,则可以使用本驱动进行数据库连接操作,查询驱动程序是否认为它可以打开到给定 URL 的连接。
一定要在第一次使用DriverManager之前设置jdbc.drivers,因为DriverManager中的static静态代码段只会被执行一次!