Spring Boot入門8
回顧
表之間的關係:
一對多|多對一: 在多的一方新增一列,並且新增外來鍵約束,指向一的一方
多對多 : 建立一張中間表,將多對多的關係拆成兩個一對多的關係,中間表至少要包含兩列,分別指向原來的兩張表的主鍵
一對一:
1.將兩張表合成一張表
2.當作一對多的情況處理,增加一列,並且新增外來鍵約束和唯一約束,指向另外一張表
3.將兩張表的主鍵填寫成一樣
多表查詢:
內連線:
select * from product p,category c where p.cno=c.cid;
select * from product p inner join category c on p.cno=c.cid
左外連線:
select * from product p left outer join category c on p.cno=c.cid;
右外連線:
select * from product p right outer join category c on p.cno=c.cid;
子查詢: 查詢語句中巢狀查詢語句
使用java操作資料庫表中資料的增刪改查
一、JDBC&連線池
1. jdbc介紹
JDBC(Java DataBase Connectivity ,java資料庫連線)是一種用於執行SQL語句的Java API,可以為多種關係資料庫提供統一訪問,它由一組用
Java語言編寫的類和介面組成。JDBC提供了一種基準,據此可以構建更高階的工具和介面,使資料庫開發人員能夠編寫資料庫應用程式。 簡言之: 通過jdbc, 可以使用java語言來操作資料庫自從Java語言於1995年5月正式公佈以來,Java風靡全球。出現大量的用java語言編寫的程式,其中也包括資料庫應用程式。由於沒有一個Java語言的API,程式設計人員不得不在Java程式中加入C語言的ODBC函式呼叫。這就使很多Java的優秀特性無法充分發揮,比如平臺無關性、面向物件特性等。隨著越來越多的程式設計人員對Java語言的日益喜愛,越來越多的公司在Java程式開發上投入的精力日益增加,對java語言介面的訪問資料庫的API的要求越來越強烈。也由於ODBC的有其不足之處,比如它並不容易使用,沒有面向物件的特性等等,SUN公司決定開發一Java語言為介面的資料庫應用程式開發介面。在JDK1.x版本中,JDBC只是一個可選部件,到了JDK1.1公佈時,SQL類包(也就是JDBCAPI)就成為Java語言的標準部件。
2. jdbc入門
- 新增
mysql 驅動
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.17'
- 入門程式碼
//1. 註冊驅動
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2. 建立連線
//DriverManager.getConnection("jdbc:mysql://localhost/test?user=monty&password=greatsqldb");
//2. 建立連線 引數一: 協議 + 訪問的資料庫 , 引數二: 使用者名稱 , 引數三: 密碼。
conn = DriverManager.getConnection("jdbc:mysql://localhost/student", "root", "root");
//3. 建立statement , 跟資料庫打交道,一定需要這個物件
st = conn.createStatement();
//4. 執行查詢 , 得到結果集
String sql = "select * from t_stu";
rs = st.executeQuery(sql);
//5. 遍歷查詢每一條記錄
while(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.println("id="+id + "===name="+name+"==age="+age);
}
//6. 關閉連線,釋放資源
if (rs != null) {
try {
rs.close();
} catch (SQLException sqlEx) { } // ignore
rs = null;
}
...
3. 註冊驅動小細節
在我們通過DriverManager 去獲取連線物件的時候,使用了一行程式碼
DriverManager.registerDriver(...)
來註冊驅動,只有註冊驅動,我們才能獲取連線物件。但是這行註冊驅動的背後,有些細節,在這裡給大家說一說。
在com.mysql.jdbc.Driver類種有一段靜態程式碼 , 只要這個Driver被載入到jvm中,那麼就會註冊驅動。所以註冊驅動的時候,可以使用文件中的寫法。 Class.forName(“com.mysql.jdbc.Driver”);
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
4. jdbc工具類抽取
1. 基本抽取
String driverClass = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql:///heima02";
String name = "root";
String password= "root";
/**
* 獲取連線物件
* @return
*/
public static Connection getConn(){
Connection conn = null;
try {
Class.forName(driverClass);
//2. 建立連線 引數一: 協議 + 訪問的資料庫 , 引數二: 使用者名稱 , 引數三: 密碼。
conn = DriverManager.getConnection(url, name, password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
/**
* 釋放資源
* @param conn
* @param st
* @param rs
*/
public static void release(Connection conn , Statement st , ResultSet rs){
closeRs(rs);
closeSt(st);
closeConn(conn);
}
private static void closeRs(ResultSet rs){
try {
if(rs != null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
rs = null;
}
}
private static void closeSt(Statement st){
try {
if(st != null){
st.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
st = null;
}
}
private static void closeConn(Connection conn){
try {
if(conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
conn = null;
}
}
2. properties使用
即便上面抽取出來了工具類,但是對於資料庫連線的配置,我們仍然是在程式碼裡面進行硬編碼,一旦我們想要修改資料庫的連線資訊,那麼操作起來就顯得不是那麼方便。所以通常會採用配置檔案,在外部宣告資料庫連線資訊,在程式碼裡面讀取配置。 這些配置資訊,未來大家會見到
properties
|xml
兩種形態的情景
- 在resource下新建一個properties檔案, 名稱隨意
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost/heima02
name=root
password=root
- 工具類種讀取配置檔案內容
static{
try {
//1. 建立一個屬性配置物件
Properties properties = new Properties();
//使用類載入器,去讀取src底下的資原始檔。 後面在servlet
InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
//匯入輸入流。
properties.load(is);
//讀取屬性
driverClass = properties.getProperty("driverClass");
url = properties.getProperty("url");
name = properties.getProperty("name");
password = properties.getProperty("password");
} catch (Exception e) {
e.printStackTrace();
}
}
4. 細節處理
其實可以省略掉
public class JDBCUtil {
static String driverClass = null;
static String url = null;
static String name = null;
static String password= null;
static{
try {
//1. 建立一個屬性配置物件
Properties properties = new Properties();
InputStream is = new FileInputStream("jdbc.properties");
//使用類載入器,去讀取src底下的資原始檔。 後面在servlet
// InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
//匯入輸入流。
properties.load(is);
//讀取屬性
driverClass = properties.getProperty("driverClass");
url = properties.getProperty("url");
name = properties.getProperty("name");
password = properties.getProperty("password");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 獲取連線物件
* @return
*/
public static Connection getConn(){
Connection conn = null;
try {
Class.forName(driverClass);
//靜態程式碼塊 ---> 類載入了,就執行。 java.sql.DriverManager.registerDriver(new Driver());
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//DriverManager.getConnection("jdbc:mysql://localhost/test?user=monty&password=greatsqldb");
//2. 建立連線 引數一: 協議 + 訪問的資料庫 , 引數二: 使用者名稱 , 引數三: 密碼。
conn = DriverManager.getConnection(url, name, password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
/**
* 釋放資源
* @param conn
* @param st
* @param rs
*/
public static void release(Connection conn , Statement st , ResultSet rs){
closeRs(rs);
closeSt(st);
closeConn(conn);
}
private static void closeRs(ResultSet rs){
try {
if(rs != null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
rs = null;
}
}
private static void closeSt(Statement st){
try {
if(st != null){
st.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
st = null;
}
}
private static void closeConn(Connection conn){
try {
if(conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
conn = null;
}
}
}
5. jdbc crud
1. sql語句回顧
- 新增
insert into student values(null , 'zhangsan' , 18);
- 刪除
delete from student where id = 5;
- 修改
UPDATE student SET name = '奧巴馬' WHERE id = 3;
- 查詢
select * from student;
2. 新增
@Test
public void testSave(){
Connection conn = null;
Statement statement = null;
try {
//1. 獲取連線物件
conn = JDBCUtil.getConn();
//2. 準備語句
statement = conn.createStatement();
//3. 執行語句
String sql = "insert into student values(null , 'zhangsansan',29)";
int result = statement.executeUpdate(sql);
System.out.println("result=" + result);
} catch (SQLException e) {
e.printStackTrace();
}finally {
//4. 釋放資源
JDBCUtil.release(conn , statement , null);
}
}
3. 刪除
@Test
public void testDelete(){
Connection conn = null;
Statement statement = null;
try {
//1. 獲取連線物件
conn = JDBCUtil.getConn();
//2. 準備語句
statement = conn.createStatement();
//3. 執行語句
String sql = "delete from student where id = 4";
int result = statement.executeUpdate(sql);
System.out.println("result=" + result);
} catch (SQLException e) {
e.printStackTrace();
}finally {
//4. 釋放資源
JDBCUtil.release(conn , statement , null);
}
}
4. 修改
@Test
public void testUpdate(){
Connection conn = null;
Statement statement = null;
try {
//1. 獲取連線物件
conn = JDBCUtil.getConn();
//2. 準備語句
statement = conn.createStatement();
//3. 執行語句
String sql = "update student set age = 88 where id = 5";
int result = statement.executeUpdate(sql);
System.out.println("result=" + result);
} catch (SQLException e) {
e.printStackTrace();
}finally {
//4. 釋放資源
JDBCUtil.release(conn , statement , null);
}
}
5. 查詢
@Test
public void testFindAll(){
Connection conn = null;
Statement statement = null;
ResultSet resultSet = null;
try {
//1. 獲取連線物件
conn = JDBCUtil.getConn();
//2. 準備語句
statement = conn.createStatement();
//3. 執行語句
String sql = "select * from student";
resultSet = statement.executeQuery(sql);
//4. 遍歷查詢結果
while(resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
System.out.println(id + " : " + name + " : " + age);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//4. 釋放資源
JDBCUtil.release(conn , statement , resultSet);
}
}
6. Statement 注入問題
statement 現在已經很少直接使用了,反而是使用PrepareStatement 來替代它。從關係上來看,PrepareStatement 是 Statement的子介面,作為它的一個擴充套件。一般企業級應用開發都會採用PrepareStatement , 之所以這麼做,是處於以下幾個原因考慮:
- PreparedStatement 可以寫動態引數化的查詢
- PreparedStatement 最重要的一點好處是它擁有更佳的效能優勢,SQL語句會預編譯在資料庫系統中
- PreparedStatement 可以防止SQL注入式攻擊
- 比起Statement 凌亂的字串追加似的查詢,PreparedStatement查詢可讀性更好、更安全。
//基本的登入操作
strSQL = "SELECT * FROM users WHERE name = '" + userName + "' and pw = '"+ passWord +"';"
//如果登入的時候,惡意寫成
userName = "1 OR 1=1";
passWord = "1 OR 1=1";
//那麼最終的登入語句就會變成這樣:
strSQL = "SELECT * FROM users WHERE name = 1 OR 1=1 and pw = 1 OR 1=1"
上面的語句只是最終的呈現結果。 在輸入使用者名稱和密碼的時候,要寫這樣:
username : 1 'or' 1=1
password : 1 'or' 1=1
7. PrepareStatement CRUD
該小節其實就是把Statement的程式碼重新修正一下。
@Test
public void testSave(){
Connection conn = null;
PreparedStatement ps = null;
try {
//1. 獲取連線物件
conn = JdbcUtil02.getConn();
//Statement st = conn.createStatement();
//st.execute("insert into student values (null , 'wangwu',18)");
//2. 建立ps物件
String sql = "insert into student values(null , ? , ?)";
ps = conn.prepareStatement(sql);
//3. 填充佔位符
ps.setString( 1, "王五");
ps.setInt(2 , 28);
//4. 執行Statement
int result = ps.executeUpdate();
System.out.println("result=" + result);
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtil02.release(conn , ps , null);
}
}
6.Dao 模式
在早前第一天的時候,就給大家講過了三層架構的概念。實際上有關資料操作的邏輯都應該是位於資料訪問這一層。
DAO
: 全稱是data access object,資料庫訪問物件,主要的功能就是用於進行資料操作的,在程式的標準開發架構中屬於資料層的操作 。 所以接下來我們會把讓程式碼變得更豐富一些,使用dao的模式來封裝資料庫操作程式碼。未來為了讓程式碼更易於管理,條理更清晰,通常都會採用分層的形式來包裝程式碼
- dao 介面
public interface StudentDao {
void save(User user);
}
- dao 實現
public class StudentDaoImpl implements StudentDao {
private static final String TAG = "StudentDaoImpl";
@Override
public void save(User user) {
Connection conn = null;
Statement statement = null;
try {
//1. 獲取連線物件
conn = JDBCUtil.getConn();
//2. 準備語句
statement = conn.createStatement();
//3. 執行語句
String sql = "insert into student values(null , 'zhangsansan',29)";
int result = statement.executeUpdate(sql);
System.out.println("result=" + result);
} catch (SQLException e) {
e.printStackTrace();
}finally {
//4. 釋放資源
JDBCUtil.release(conn , statement , null);
}
}
}
為什麼service 和 dao都需要寫介面呢?
這是一種程式設計思想, 多型的思想。
介面的作用是用於宣告功能,定義標準、規範。 實現類必須在介面規範下實現邏輯。
a。 我不用介面,我就是一個類,但是我也很規範,很標準。
介面宣告規範,實現類做具體的實現,那麼就是宣告和實現分開了。
面向介面程式設計
如果沒有介面,就一個純粹的具體類。 張三做了A模組,正好要呼叫李四的B模組的某個方法。
StudentDao dao = new StudentDaoImpl();
- 介面的作用是用於宣告功能,定義標準、規範。 實現類必須在介面規範下實現邏輯。
- 介面的好處是在協作時,大家可以面向介面程式設計,無需關心具體是如何實現的。
- 介面的宣告,如果不夠優秀,可以在後期擴充套件子介面,這可以體現迭代更新的記錄。思考的例子就是: Statement 和 PrepareStatement
7. 連線池
1. 介紹
資料庫連線是一種關鍵的、有限的、昂貴的資源,這一點在多使用者的網頁應用程式中體現得尤為突出。對資料庫連線的管理能顯著影響到整個應用程式的伸縮性和健壯性,影響到程式的效能指標。資料庫連線池正是針對這個問題提出來的
連線池基本的思想是在系統初始化的時候,將資料庫連線作為物件儲存在記憶體中,當用戶需要訪問資料庫時,並非建立一個新的連線,而是從連線池中取出一個已建立的空閒連線物件。使用完畢後,使用者也並非將連線關閉,而是將連線放回連線池中,以供下一個請求訪問使用。而連線的建立、斷開都由連線池自身來管理。同時,還可以通過設定連線池的引數來控制連線池中的初始連線數、連線的上下限數以及每個連線的最大使用次數、最大空閒時間等等。也可以通過其自身的管理機制來監視資料庫連線的數量、使用情況等
2. 常見的連線池
DBCP | c3p0 | hikari CP | Druid
3. C3P0
//新增依賴
compile group: 'com.mchange', name: 'c3p0', version: '0.9.5.2'
- 程式碼版本
@Test
public void testC3p0(){
try {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/heima02");
dataSource.setUser("root");
dataSource.setPassword("root");
//設定初始化連線數
dataSource.setInitialPoolSize(5);
//設定最大連線數
dataSource.setMaxPoolSize(10);
//獲取連線
Connection conn = dataSource.getConnection();
//操作資料庫
//...
//3. 執行語句
String sql = "insert into student values(null ,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,"lisi");
ps.setInt(2,19);
ps.executeUpdate();
//釋放資源..回收連線物件
ps.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
- 配置版本
一般連線池的配置都是採用配置檔案來宣告,很少有使用程式碼來配置連線池的。c3p0的配置檔案形式可以選擇使用xml 或者是用 properties形態。
- 在resource下新建一個xml檔案,名稱為 c3p0-config.xml 內容如下:
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///heima02</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>
</default-config>
</c3p0-config>
- 程式碼
@Test
public void testC3p0(){
try {
//預設會讀取xml檔案內容。 所以xml的名字必須固定。
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//獲取連線
Connection conn = dataSource.getConnection();
//操作資料庫
//...
//3. 執行語句
String sql = "insert into student values(null ,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,"lisi2");
ps.setInt(2,19);
ps.executeUpdate();
//釋放資源..回收連線物件
ps.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
8. DBUtils
dbutils 是 Apache 開發的一套封裝了jdbc操作的jar包。使用它,我們可以不用再宣告以前那些的物件了。它的主要操作方法就兩個:
update
和query
, update方法針對 增刪改 , query方法針對查詢。
- 匯入約束
compile group: 'commons-dbutils', name: 'commons-dbutils', version: '1.6'
- 增加
@Override
public void insert(Student student) throws SQLException {
QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
runner.update("insert into stu values(null , ?,?,?)" ,
student.getSname(),
student.getGender(),
student.getPhone());
}
- 刪除
@Override
public void delete(int sid) throws SQLException {
QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
runner.update("delete from stu where sid=?" ,sid);
}
- 更新
@Override
public void update(Student student) throws SQLException {
QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
runner.update("update stu set sname=? , gender=? , phone=? ",
student.getSname(),
student.getGender(),
student.getPhone();
}
- 查詢
@Override
public List<Student> findAll() throws SQLException {
QueryRunner runner = new QueryRunner(JDBCUtil02.getDataSource());
return runner.query("select * from stu", new BeanListHandler<Student>(Student.class));
}
總結:
JDBC: JAVA DATABASE CONNECTIVITY java 資料庫連線
為什麼要學習jdbc?
因為不同的資料庫,它的操作方式/方式是不一樣,我們希望有一個統一的介面
mysql -uroot -proot , sqlplus 使用者名稱/密碼
JDBC開發步驟:
1.註冊驅動: class.forName(驅動類的全路徑)
2.獲取連線: DriverManager.getConnection(url,username,password)
3.獲取執行SQL的物件: connection.createStatement(),connection.prepareStatement()
statement : 會存在Sql注入的問題
prepareStatement : 不會出現, insert into user values(?,?,?)
setXXX 設定引數,引數的起始角標是從1開始
4.執行SQL
executeUpdate :增刪改的操作,返回影響行數
executeQuery: 執行查詢的操作,ResultSet
5. 釋放資源
以後將來我們使用的都是PrepareStatement
Dao模式? 降低程式碼耦合
IUserDao --- 定義/約束方法名稱 ---> UserDaoImpl
連線池: 為了提高效率,程式一啟動,就準備好若干個連線,當需要使用的時候,直接從池子中去獲取
C3P0:
c3p0-config.xml 名稱必須是固定,必須放在resources下面
new ComboPooledDataSource(); 通常一個程式只會建立一個連線池
DBUtils:
QueryRunner(池子)
update(sql,params...)
query(sql,ResultSetHandler,params...)
BeanHandler<User>(User.class);
BeanListHandler<User>(User.class)