JDBC是Java数据库连接技术的简称,提供连接各种常用数据库的能力。
JDBC API:
- 实现Java程序对各种数据库的访问
- 一组接口和类,位于java. sql与javax. sqI包
- 面向接口编程
学习方法:
- JDBC步骤固定,理解记忆
- 多练习,加深理解
JDBC访问数据库步骤:
DriverManager:依据数据库的不同,管理JDBC驱动
Connection:负责连接数据库并担任传送数据的任务
Statement:由 Connection 产生、负责执行SQL语句
ResultSet:负责保存Statement执行后所产生的查询结果
通过JDBC连接数据库:
操作步骤:
- Class. forName(String)加载驱动
- 获得数据库连接(Connection)
- 创建Statement或PreparedStatement对象、执行sql语句
- 返回并处理执行结果(若查询操作,返回ResultSet)
- 释放资源
JDBC驱动由数据库厂商提供:
在个人开发与测试中,可以使用JDBC-ODBC桥连方式
在生产型开发中,推荐使用纯Java驱动方式
使用JDBC-ODBC桥方式连接数据库:
- 将对JDBC API的调用,转换为对另一组数据库连接API的调用
- 优点:可以访问所有ODBC可以访问的数据库
- 缺点:执行效率低、功能不够强大
使用JDBC-ODBC进行桥连:
- 配置数据源:控制面板 ---> ODBC数据源 ---> 系统DNS
- 编程获取连接
示例:
使用纯Java方式连接数据库:
- 由JDBC驱动直接访问数据库
- 优点:完全Java代码,快速、跨平台
- 缺点:访问不同的数据库需要下载专用的JDBC驱动
示例:
JDBC编程模板:
使用JDBC操作数据库--增删改查:
Statement常用方法:
方法名 |
说 明 |
ResultSet executeQuery(String sql) |
执行SQL查询并获取到ResultSet对象 |
int executeUpdate(String sql) |
可以执行插入、删除、更新等操作,返回值是执行该操作所影响的行数 |
boolean execute(String sql) |
可以执行任意SQL语句,然后获得一个布尔值,表示是否返回ResultSet |
ResultSet常用方法:
方法名 |
说 明 |
boolean next() |
将游标从当前位置向下移动一行 |
boolean previous() |
游标从当前位置向上移动一行 |
void close() |
关闭ResultSet 对象 |
int getInt(int colIndex) |
以int形式获取结果集当前行指定列号值 |
int getInt(String colLabel) |
以int形式获取结果集当前行指定列名值 |
float getFloat(int colIndex) |
以float形式获取结果集当前行指定列号值 |
float getFloat(String colLabel) |
以float形式获取结果集当前行指定列名值 |
String getString(int colIndex) |
以String 形式获取结果集当前行指定列号值 |
String getString(String colLabel) |
以String形式获取结果集当前行指定列名值 |
示例:(使用statement进行查询)
//查询新闻id、标题和摘要
public static void main(String[] args) {
Connection connection=null;
Statement stmt=null;
ResultSet rs =null;
try {
//加载不同的数据库厂商提供的驱动
//Class.forName()的意思是根据字符串来找到这个驱动和以前写的Driver d = new Driver();一样 ,就是创建了一个Driver的驱动化示例
Class.forName("com.mysql.jdbc.Driver");
//铺路(获取连接Connection)
String url="jdbc:mysql://127.0.0.1:3306/kgcnews";
//连接每一个数据库的URL是固定的一搜就可以了
//把Driver驱动的这个实例放到DriverManager这个应用管理器里,使用驱动的getConnection()方法,;来获得数据库连接
connection = DriverManager.getConnection(url,"root", "08170327");
//(2)下圣旨(sql命令)
String sql = "SELECT id,title FROM news_detail";
//(3)找一个小太监帮皇上执行圣旨(statement/PreparedStatement)
stmt = connection.createStatement();
//(4)拉回西瓜(返回结果集resultSet)
rs = stmt.executeQuery(sql);
while(rs.next()){
//int id = rs.getInt(1);
//String title = rs.getString(2);
int id = rs.getInt("id");
String title = rs.getString("title");
System.out.println(id+"\t"+title);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally{
//(5)关闭城门(释放资源)
try {
rs.close();
stmt.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
} } }
Statement与PreparedStatement区别:
1. PreparedStatement接口继承Statement
2. Statement st = connection. createStatement () ;
3. PreparedStatement pstm = connection. prepareStatement (sql) ;
a. SQL语句使用“?”作为数据占位符
b. 使用setXxx()方法设置数据
c. executeQuery()括号里面不填东西
4. PreparedStatement--预编译
Statement会把SQL语句发给数据库管理系统DBMS,检查语法,然后编译,然后执行
而PreparedStatement就会先把SQL语句检查编译完了,再在发给DBMS执行
5. PreparedStatement效率更高、性能更好、开销更小、安全性更高、代码可读性更好
示例:(使用PreparedStatement进行增删改查)
//使用JDBC实现课工场新闻数据的增删改查
public class NewsDao3 {
Connection connection=null;
PreparedStatement ptmt=null;
ResultSet rs =null;
//提取重复的代码,减少冗余
//获取数据库连接
public void getConnection(){
//加载不同的数据库厂商提供的驱动
//Class.forName()的意思是根据字符串来找到这个驱动和以前写的Driver d = new Driver();一样 ,就是创建了一个Driver的驱动化示例
try {
Class.forName("com.mysql.jdbc.Driver");
//铺路(获取连接Connection)
String url="jdbc:mysql://127.0.0.1:3306/kgcnews";
//连接每一个数据库的URL是固定的一搜就可以了
//把Driver驱动的这个实例放到DriverManager这个应用管理器里,使用驱动的getConnection()方法,;来获得数据库连接
connection = DriverManager.getConnection(url,"root", "08170327");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
//增加新闻信息的方法
public void addNews(int id,int categoryid,String title,String summary,String content,String author,Date createdate){
this.getConnection();
try{
String sql = "INSERT INTO news_detail(id,categoryid,title,summary,content,author,createdate) VALUES(?,?,?,?,?,?,?)";
ptmt = connection.prepareStatement(sql);
ptmt.setInt(1,id);
ptmt.setInt(2,categoryid);
ptmt.setString(3,title);
ptmt.setString(4,summary);
ptmt.setString(5,content);
ptmt.setString(6,author);
ptmt.setTimestamp(7,new Timestamp(createdate.getTime()));
//增删改都用executeUpdate()
int i = ptmt.executeUpdate();
if(i>0){
System.out.println("插入新闻成功!");
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
ptmt.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
} } }
//删除特定新闻的方法
public void deleteNews(int id){
this.getConnection();
try{
String sql = "DELETE FROM news_detail WHERE id=?";
ptmt = connection.prepareStatement(sql);
ptmt.setInt(1,id);
int i = ptmt.executeUpdate(); //增删改都用executeUpdate()
if(i>0){
System.out.println("删除新闻成功!");
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
ptmt.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
} } }
//修改特定新闻标题的方法
public void updateNews(int id,String title){
this.getConnection();
try{
String sql = "UPDATE news_detail SET title =? WHERE id=?";
ptmt = connection.prepareStatement(sql);
ptmt.setString(1,title);
ptmt.setInt(2,id);
int i = ptmt.executeUpdate(); //增删改都用executeUpdate()
if(i>0){
System.out.println("修改新闻成功!");
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
ptmt.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
} } }
//查询全部新闻信息
public void getNewsList(){
this.getConnection();
try {
this.getConnection();
String sql = "SELECT id,categoryid,title,summary,content,author,createdate FROM news_detail";
ptmt = connection.prepareStatement(sql);
rs = ptmt.executeQuery();
while(rs.next()){
int id = rs.getInt("id");
int categoryid = rs.getInt("categoryid");
String newsTitle = rs.getString("title");
String summary = rs.getString("summary");
String content = rs.getString("content");
String author = rs.getString("author");
Timestamp createdate = rs.getTimestamp("createdate");
System.out.println(id+"\t"+categoryid+"\t"+newsTitle+"\t"+summary+"\t"+content+"\t"+author+"\t"+createdate);
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
ptmt.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
} } }
//查询特定标题的新闻信息
public void getNewsByTitle(String title){
try {
this.getConnection();
//(2)下圣旨(sql命令)
//使用PreparedStatement就不用拼字符串了
//拼字符串容易出错
//拼字符串可读性太差
//使用?,?是占位符具体想传谁,?就是谁
String sql = "SELECT id,title FROM news_detail where title=?";
//(3)找一个小太监帮皇上执行圣旨(statement/PreparedStatement)
ptmt = connection.prepareStatement(sql);
//在SQL语句的第一个问号的位置填充title
ptmt.setString(1,title);
//System.out.println(sql);
//(4)拉回西瓜(返回结果集resultSet)
rs = ptmt.executeQuery();
while(rs.next()){
//int id = rs.getInt(1);
//String title = rs.getString(2);
int id = rs.getInt("id");
String newsTitle = rs.getString("title");
System.out.println(id+"\t"+newsTitle);
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
//(5)关闭城门(释放资源)
try {
rs.close();
ptmt.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
} } }
public static void main(String[] args) {
NewsDao3 dao2 = new NewsDao3();
//dao2.getNewsByTitle("Java Web开课啦");
//dao2.getNewsByTitle("Java Web开课啦");
//如果语句是:"Java Web开课啦' or '1'='1"
//就会拼成SELECT id,title FROM news_detail where title='Java Web开课啦' or '1'='1'
//只要有一个true就会把全部信息查出来,这个叫做SQL漏洞
//dao2.addNews(4, 1, "test", "test", "test", "maoxin", new Date());
//dao2.deleteNews(2);
//dao2.updateNews(3, "newTitle");
//dao2.getNewsList();
} }
Class.forName("") 【后加载】
后加载是一开始不知道创建哪个类对象,运行的时候给一个字符串,根据这个字符串找到他对应的类,然后才加载到Java虚拟机中去,然后再创建类和对象,这个叫做反射
反射机制就是指事先不知道任何信息,在Java运行期间,才知道要去使用哪个类,如何去创建,获取类的相关信息(包括创建类和对象,包括获得这个类的方法,包括获得类的属性)
Student s = new Student(); 【先加载】
先加载是预先知道类型之后直接加载到Java虚拟机,先有这个信息然后根据这个信息创建对象,先把Student加载到Java虚拟机然后才能new:JVM ---> Student
数据库操作优化思路:
- 提取数据库公共操作(获取数据库连接、释放资源、增删改、查)形成一个数据库操作的基类
- 将NewsDao提取一个稳定的新闻操作的接口
编写完增删改查的方法的弊端:
- 将相似功能的代码抽取封装成方法,减少代码冗余
- 因为不同的数据库会有不同的实现,对数据库的操作一般抽取成接口,在以后的开发中可以降低耦合
- 数据库发生改变时,要重新修改代码,重新编译和部署:
将数据库信息写在配置文件当中,让程序通过读取配置文件来获得这些信息
配置文件:属性文件 .properties (都是键值对的形式);
为了解决弊端要进行封装,封装的方案:
1. 隔离业务逻辑代码和数据访问代码
2. 隔离不同数据库的实现
3. 让用户脱离程序本身修改相关的变量设置——使用配置文件
Java中的配置文件常为properties文件
- 后缀为.properties
- 格式是“键=值”格式
- 使用“#”来注释
示例:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.connection.url=jdbc:mysql://127.0.0.1:3306/kgcnews
jdbc.connection.username=root
jdbc.connection.password=08170327
Java中提供了Properties类来读取配置文件:
方法名 |
说明 |
String getProperty(String key) |
用指定的键在此属性列表中搜索属性。通过参数key得到其所对应的值 |
Object setProperty(String key,String value) |
调用Hashtable的方法put。通过调用基类的put()方法来设置键-值对 |
void load(InputStream inStream) |
从输入流中读取属性列表 (键和元素对)。通过对指定文件进行装载获取该文件中所有键-值对 |
void clear() |
清除所装载的键-值对,该方法由基类Hashtable提供 |
使用配置文件的方法:
a. 读取数据库属性文件,获取数据库连接信息
b. 使用静态方法:
①使用了静态之后就直接ConfigManager.getInstance().getString("jdbc.driver");就可以了
②提供给别人一个唯一的ConfigManager对象,如果不存在就new一个,如果存在就直接return
③使用静态的方法,不定义成static的话,用户只能new一个对象,但是已经定义成了私有方法,所以不能new;只能定义成静态方法,好处就是不用new对象直接类名就行
private static ConfigManager configManager;
c. 单例模式(让用户只能创建一个ConfigManager):
ConfigManager对象在内存里只有一个示例,不能重复new,只要构造一次就可以
①使用单例模式的时候:先改成私有的就不能随便new了
private ConfigManager()
②把程序提供给别人唯一对象
d. 获取文件名:
String configFile = "database.properties";
e. 使用文件流输出文件中的内容:
①InputStream是一个文件流;把database.properties这个文件通过ConfigManager里的类加载器getClassLoader()里的getResourceAsStream()方法就可以把一个资源变成一个流
②这个方法就可以把配置文件读到输入流里面去,这个方法找配置文件会在根目录下找文件,所以配置文件一定要写在src的根目录下
InputStream in = ConfigManager.class.getClassLoader().getResourceAsStream(configFile);
f. 把数据流加进来
通过properties的load方法把输入流加载进来
properties.load(in);
g. 根据键取出值
通过getProperty()方法就可以根据属性文件中的键获得对应的值
properties.getProperty(key);
示例:
public class ConfigManager {
private static ConfigManager configManager;
private Properties properties;
private ConfigManager(){
String configFile = "database.properties";
InputStream in = ConfigManager.class.getClassLoader().getResourceAsStream(configFile);
properties = new Properties();
try {
properties.load(in);
in.close();
} catch (IOException e) {
e.printStackTrace();
} }
public static synchronized ConfigManager getInstance(){
if(configManager == null){
configManager=new ConfigManager();
}
return configManager;
}
public String getString(String key){
return properties.getProperty(key);
} }
单例模式有两种实现方式:
1. 饿汉方式:
不管调不调用都new出来这个对象,new对象的时机是只要加载这个类到虚拟机里就会加载静态方法new一个对象,此时不管调不调用getInstance()
饿汉方式不存在线程安全的问题
示例:
public class ConfigManager {
private static ConfigManager configManager = new ConfigManager;
private Properties properties;
private ConfigManager(){
String configFile = "database.properties";
InputStream in = ConfigManager.class.getClassLoader().getResourceAsStream(configFile);
try {
properties.load(in);
in.close();
} catch (IOException e) {
e.printStackTrace();
} }
public static ConfigManager getInstance(){
return configManager;
}
public String getString(String key){
return properties.getProperty(key);
} }
2. 懒汉方式:
- 当需要用这个方法的时候,再去new这个对象,并且保证就new一个
- 懒汉方式的缺点是线程不安全,如果两个人同时调用,就会有可能内存里还是出现了多个configManager对象
- 解决方法是把方法改成public static synchronized ConfigManager getInstance()
- synchronized的意思是同步,意思是上了一个锁,第一个人完全执行之后,第二个人才能进入,这就避免了线程不安全
示例:
最开始的就是这个例子
4. 使用实体类传递数据
数据访问代码和业务逻辑代码之间通过实体类来传输数据
实体类特征:
- 属性一般使用private修饰
- 提供public修饰的getter/setter方法
- 实体类提供无参构造方法,根据业务提供有参构造
- 实现java.io.Serializable接口,支持序列化机制