内容
- jdbc概述
- 数据库连接
- 使用PreparedStatement实现crud
- Blob操作
- 批量插入
- 事物
- 连接池
- apache的DBUtils
javaweb最简版的架构(c/s b/s)
B/S技术体系
html:浏览器骨架 超文本标记语言
css:炫酷的页面 静态显示
JavaScript:行为
jQuery:是一个库,封装了js;也是一个前端框架(其它前端框架:vue易、react难、ajs国内不怎么用了)
tomcat:服务器
xml:可扩展标记语言 写配置文件
servlet:tomcat组件——获取请求、处理请求、响应请求
jsp:响应请求的页面显示 动态显示 本质也是servlet
EL:替代jsp表达式
JSTL:替代jsp脚本中断
cookie:识别是否来自同一个浏览器 客户端
session:识别是否来自同一个浏览器 服务器端
filter:tomcat组件
listener:tomcat组件
ajax:实现异步请求,部分改变
json:数据交换格式,传输数据用的
1.jdbc概述
1.1 数据持久化:断电之后数据不会消失
1.2 Java中的数据存储技术:
- jdbc Java数据库连接 直接访问数据库 独立于特定的数据库
- jdo Java data object
- 第三方O/R工具 一些框架如hibernate和mybatis
1.3 JDBC提供统一接口,实现依赖具体的数据库,由数据库厂商提高实现类
1.4 JDBC编程步骤
- 导入java.sql包
- jdbc-odbc桥接式/纯jdbc驱动
- 加载并注册驱动程序(java.sql.*)
- 创建Connection对象
- 创建Statement对象
- 执行sql
- 使用ResultSet对象
- 关闭资源:ResultSet 对象、Statement对象、Connection对象
2.数据库连接
2.1 驱动Driver获得连接对象
方式一:
Driver driver=new com.mysql.jdbc.Driver();//实现类of java.sql.Driver
String url="jdbc:mysql://localhost:3306/test";//jdbc url:协议:子协议(标识数据库驱动程序)://子名称(标识数据库=主机名:端口号/数据库名称)
Properties prop=new Properties();
prop.setProperty("user","root");
prop.setProperty("password","123");
Connection connection=driver.connect(url,prop);
方式二:
//不希望出现第三方com.sql.jdbc.Driver,反射来解决
Class clazz=Class.forName("com.mysql.jdbc.Driver");
Driver driver=(Driver)clazz.newInstance();
...//与方式一一样获取connection
方式三:
String url="jdbc:mysql://localhost:3306/test";
String user="root";
String password="123";
Class clazz=Class.forName("com.mysql.jdbc.Driver");
Driver driver=(Driver)clazz.newInstance();
DriverManager.register(driver);
Connection conn=DriverManager.getConnection(url,user,password);
方式四:
String url="jdbc:mysql://localhost:3306/test";
String user="root";
String password="123";
//省略掉的方式三中的部分是因为mysql的Driver中已在静态代码块中以实现
Class.forName("com.mysql.jdbc.Driver");
Connection conn=DriverManager.getConnection(url,user,password);
方式五(推荐):
jdbc.properties
diver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
user=root
password=123
//读取配置文件的方式jdbc.properties
InputStream resource=当前类.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop=new Properties();
prop.load(resource);
String driver=prop.getProperty("driver");
String url=prop.getProperty("url");
String user=prop.getProperty("user");
String password=prop.getProperty("password");
Class.forName(driver);
Connection conn=DriverManager.getConnection(url,user,password);
方式五的优势:换数据库只需要改配置文件即可,不需要改代码,解耦数据与代码
2.2 Statement
通过conn.createStatement()来获取
该对象调用 int executeUpdate(String sql)/ResultSet executeQuery(String sql)来执行sql语句
其弊端:
- 问题一:存在拼接,操作繁琐 eg “select name from user where id=”+id;
- 问题二:存在SQL注入(不充分检查数据而造成非法sql)
PreparedStatement可以避免Statement弊端,此外其具有更高的效率,因为预编译所以对于只是参数不同的同一条sql只编译一次,而Statement会编译n次
3.使用PreparedStatement实现crud
PreparedStatement对象通过conn.prepareStatement()来获取
//准备sql
try{
Connection conn=JDBCUtils.getConnection();//使用工具类,工具类具体内容在下面
//增
String sql="insert into user(id,name,birth) values(?,?,?)";//?为占位符,占位符从1开始编号
PreparedStatement ps=conn.prepareStatement(sql);
ps.setString(1,"202001");
ps.setString(2,"煤气罐");
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
java.util.Date date=sdf.parse("2008-02-02");
ps.setDate(3,date);
//执行sql
ps.execute(sql);
//改
String sql="update user set name=? where id=?";
PreparedStatement ps=conn.preparsStatment(sql);
ps.setObject(1,"tom");
ps.setInt(2,11);
ps.execute();
//通用增删改
String sql="";
JDBCUtils.execute(conn,sql,参数们);
//查
String sql="select * from user";
PreparedStatment ps=conn.prepareStatement(sql);
ResultSet set=ps.executeQuery();
while(set.next()){
User u=new User();//表对应的pojo
u.setId(set.getString(1));
u.setString(set.getString(2));
u.setBirth(set.getDate(3));
System.out.println(u.toString());
}
//通用查
String sql="";
JDBCUtils.executeQuery(conn,sql,参数们);
}catch(Exception e){
e.printStackTrace();
}finally{
//关闭资源
BCUtils.close(conn,ps);
}
ORM思想 object relational mapping
- 一个表对应一个类
- 表中字段对应类的属性
- 一条记录对应一个类的实例对象
JDBCUtils
!!这里举例的查询通用方法只是显示,可以优化成返回结果集,然后再编写一个处理结果集的工具类
public class JDBCUtils{
private Connection conn=null;
static{
InputStream resource=ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties prop=new Properties();
prop.load(resource);
String driver=prop.getProperty("driver");
String url=prop.getProperty("url");
String user=prop.getProperty("user");
String password=prop.getProperty("password");
Class.forName(driver);
conn=DriverManager.getConnection(url,user,password);
}
//获取连接
public static Connection getConnection() throws Exception{
return conn;
}
//关闭资源
public static void close(Connection conn, Statement s){//PreparedStatement继承于Statement
try{if(s!=null)
s.close();
}catch(...){...}
try{if(conn!=null)
conn.close();
}catch(...){...}
}
public static void close(Connection conn,Statement s,ResultSet result){
try{}catch...//同上,多一个result的关闭
}
//通用增删改
public static void execute(Connection conn,String sql,Object ...args) throws Exception{
PreparedStatement ps=conn.prepareStatement(sql);
for(int i=0;i<args.length;i++){
ps.setObject(i+1,args[i]);
}
ps.execute();
}
//通用查,这里只针对某个表的所有查询,不是针对不同的表
public static void executeQuery(Connection conn,String sql,Object ...args){
PreparedStatement ps=conn.prepareStatement(sql);
for(int i=0;i<args.length;i++)
ps.setProperty(i+1,args[i]);
ResultSet result=ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rs=result.getMetaData();
int column=rs.getColumnCount();
while(result.next()){
User u=new User();
for(int i=0;i<column;i++){
Object o=result.getObject(i+1);
//获取列名
String cn=rs.getColumnName(i+1);
//赋给属性
Field f=User.class.getDeclaredField(cn);//当属性名和表字段名不一致时not OK
String cn=rs.getColumnLabel(i+1);//如果没其起别名就返回列名,以后不使用getColumnName()
Field f=User.class.getDeclaredField(cn)//要在sql中给字段起别名,即同属性名,才可使这个语句正确
f.setAccessible(true);//以防属性为私有
f.set(u,f);
}
System.out.println(u.toString());
}
}
//通用查,针对不同表
public static void executeQuery(Connection conn,String sql,Class<T> clazz,Object ...args){
...//相同代码省略
while(result.next()){
T t=clazz.newInstance();
...
}
}
}
JDBC API小结
- 两种思想:面向接口编程,ORM思想
- 两种技术:获取元数据集,反射
4.Blob(tinyblob 255 blob 65k mediumblob 16M longblob 4G)二进制大型数据类型
FileInputStream in=new FileInputStream(new File("1.jpg"));
ps.setBlob(5,in);
//别忘了关闭流
//获取结果集中blob
Blob photo=result.getBlob(5);
Inputstream binaryStream=photo.getBinaryStream();
FileOutputStream out=new FileOutputStream("2.jpg");
byte[] b=new byte[1024];
int len;
while((len=binaryStream.read(b))!=-1){
out.write(b,0,len);
}
注意事项:
- 依据数据大小选合适的blob类型
- 文件过大会降低数据库性能
- Statement无法操作blob数据
- 如果不是因为类型原因装不下文件,就去改my.ini:max_allowed_packet=16M。!!修改了my.ini要重启数据库才生效
5.批量操作
!!使用PreparedStatement实现高效的批量插入
PreparedStatement | Statement |
---|---|
预编译,可复用编译的语句 | 不能预编译,导致相同执行语句因为数据不一样要重新编译 |
!!编译意味着要语法检查、语义检查,语句翻译所以费时,预编译需要缓存编译好的内容,以以空间换时间
//这里记得要try-catch一下,有资源关闭的地方都需要,为了关注批量操作本身这里省略没写
//注意变量定义要放在try-catch语句之外
Connection conn=JDBCUtils。getConnection();
conn=setAutoCommit(false);//不允许自动提交,别执行就提交,让它最后提交,优化速度
String sql="insert into user(name) values(?)";
PreparedStatement ps=conn.prepareStatement(sql);
for(int i=0;i<=20000;i++){
ps.setObject(1,"user_"+i);
//攒sql
ps.addBatch();//添加批处理的参数
if(i>0&&i%500=0){
//执行batch
ps.executeBatch();
//清空batch
ps.clearBatch();
}
}
conn.commit();
JDBCUtils.close(conn,ps);
!!mysql正常情况下是关闭批处理的,需要在配置文件中开启:写在url后面rewriteBatchedStatements=true
jdbc.properties
diver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
user=root
password=123
6.事物
6.1 概念:一组逻辑操作单元(DML操作,简单来说就是增删改),使数据从一种状态变为另一种一致的状态
6.2 原则:要么全做成功,要么全部没做,所以有失败则回滚
!!数据一旦提交就不可回滚
6.3 会自动提交的操作
- DDL,且set autocommit=false对其设置无效
- DML,可以通过set autocommit=false来阻止其自动提交
- 默认再关闭数据库连接时会自动提交
try{
conn.setAutoCommit(false);
事物
conn.commit();
}
catch(Exception e){
conn.rollback();
略
}
finally{
conn.setAutoCommit(true);//以防别人要用自动提交,针对数据库连接池
关闭资源
}
6.4 ACID属性
- 原子性automicity:事物是不可分割的工作单元,要么全执行,要么全不执行
- 一致性consistency:事物从一个一致状态转换为另一个一致性状态
- 隔离性isolation:并发事物互不干扰
- 持久性durability:事物一旦提交,就是永久性改变,接下来发生的任何事情对它都没有影响
6.5 数据库并发三个问题
- 脏读:两事物,T1改了数据但未提交,T2读到了T1改了的数据
- 不可重复读:T2读数据,T1改数据并提交,T2又又读了一遍,发现两次结果不一样
- 幻读:T2读数据,T1增加了几行数据并提交,T2又又读了一遍,发现多了几行
6.5 隔离级别
- read uncommitted:三问题都没解决
- read commited:解决了脏读 Oracle默认(Oracle支支持2和4)
- repeatable read:解决了不可重复读和脏读 MySQL默认 (mysql都支持)
- serializable:三个问题都解决了
!!一致性越好性能越差,即并发性越差
6.7 MySQL中的命令
创建用户:create user 用户名 identified by ‘密码’;
赋予权限:grant select,insert,update,delete on 库名.* to 用户名@主机名 identified by ‘密码’;
查看隔离级别:select @@tx_isolation;
设置隔离级别:set global transaction isolation level 隔离级别;
//获取隔离级别
conn.getTransactionIsolation();
//设置隔离级别
conn.setTransctionIsolation(Connection.TRANSACTION_READ_COMMITED);
7.数据库连接池
7.1 普通方式获取连接及关闭连接的弊端
- 连接的获取需要放到内存中占用内存资源,并需要进行用户名和密码的验证,费时费力;如果执行完操作就关闭连接,会造成资源浪费
- 如果未关闭成功会造成内存泄漏,甚至需要重启数据库(java中内存泄漏指不能被垃圾回收)
- 连接数量不可控制,造成数据库崩溃
7.2 数据库连接池可以解决这些问题
- 预先放在池中一些连接,需要时从里面拿,用完放回
- 依需增加连接和减少连接,对连接进行管理
7.3 JDBC开源的数据库连接池(DataSource的实现类)
- DBCP:apache提供,tomcat自带,速度比C3P0快,但有一些自身bug BasicDataSource,它用工厂模式创建
- C3P0:速度较慢,但稳定 ComboPooledDataSource
- Druid:阿里提供,既稳定又快 DruidDataSource
- Proxool:比C3P0性能差一点
- BonCP:速度快
!!使用哪个要导入其jar包,参考文档使用
7.4 数据源(DataSource 接口):连接池+连接池管理 使用DataSource代替DriverManager获取连接高效快速
以C3P0为例
数据源对象ds
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("...");
ds.setUser("..");
ds.setPassword("...");
ds.setInitialPoolSize(10);
等等设置
DataSource.destroy(ds);//销毁连接池
!!也可通过配置文件xml操作 cpds=new ComboPooledDataSource(“配置文件名”);
在JDBCUtils中使用连接池获取连接
private static ComboPooledDataSource cpds=new ComboPooledDataSource();//池子有一个就可以了
public static Connection getConnection() throws SQLException{
return cpds.getConnection();
}
8.Apache—DBUtils
替代我们自己编写的JDBCUtils
API:
- org.apache.commons.dbutils.QueryRunner:增删改查操作,这是个类
- org.apache.commons.dbutils.ResultSetHandler:结果集处理器,这是个接口
- org.apache.commons.dbutils.DbUtils:关闭资源的操作,这是个类