1. 程式人生 > >Spring Boot入門8

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 兩種形態的情景

  1. 在resource下新建一個properties檔案, 名稱隨意
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost/heima02
name=root
password=root
  1. 工具類種讀取配置檔案內容
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 , 之所以這麼做,是處於以下幾個原因考慮:

  1. PreparedStatement 可以寫動態引數化的查詢
  2. PreparedStatement 最重要的一點好處是它擁有更佳的效能優勢,SQL語句會預編譯在資料庫系統中
  3. PreparedStatement 可以防止SQL注入式攻擊
  4. 比起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都需要寫介面呢?

    1. 這是一種程式設計思想, 多型的思想。

    2. 介面的作用是用於宣告功能,定義標準、規範。 實現類必須在介面規範下實現邏輯。

    a。 我不用介面,我就是一個類,但是我也很規範,很標準。

    1. 介面宣告規範,實現類做具體的實現,那麼就是宣告和實現分開了。

    2. 面向介面程式設計

    如果沒有介面,就一個純粹的具體類。 張三做了A模組,正好要呼叫李四的B模組的某個方法。

    StudentDao dao = new StudentDaoImpl();

    1. 介面的作用是用於宣告功能,定義標準、規範。 實現類必須在介面規範下實現邏輯。
    2. 介面的好處是在協作時,大家可以面向介面程式設計,無需關心具體是如何實現的。
    3. 介面的宣告,如果不夠優秀,可以在後期擴充套件子介面,這可以體現迭代更新的記錄。思考的例子就是: 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形態。

  1. 在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>
  1. 程式碼
 @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包。使用它,我們可以不用再宣告以前那些的物件了。它的主要操作方法就兩個:updatequery , 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)

這裡寫圖片描述