jdbc

0.其它

jdbc.url=jdbc:mysql:///mybatis?useUnicode=true&characterEncoding=UTF8&allowMultiQueries=true
jdbc:mysql:///hib_demo?createDatabaseIfNotExist=true

1.jdbc概述

数据库驱动
JDBC是Java数据库连接技术的简称,提供连接各种常用数据库的能力
SUN公司为了简化、统一对数据库的操作,定义了一套Java操作数据库的规范,称之为JDBC。
Sun公司为简化数据库开发,定义了一套jdbc接口,这套接口由数据库厂商去实现,这样,开发人员只需要学习jdbc接口,并通过jdbc加载具体的驱动,就可以操作数据库。
JDBC全称为:Java Data Base Connectivity(java数据库连接),它主要由接口组成。
组成JDBC的2个包:
    java.sql
    javax.sql
开发JDBC应用需要以上2个包的支持外,还需要导入相应JDBC的数据库实现(即数据库驱动)。

2.jdbc作用

使用java代码操作数据库

3.jdbc连接模式

因为市面上有很多不同的数据库,那么对于开发人员来说,就要学习不同的数据库驱动来操作不同的数据库。如果数据库更新了,驱动也有可能更新,学习成本太高。
那么有了jdbc以后,开发者就不用学习那么多数据库驱动了,只需要学习jdbc即可。
jdbc是接口,是规范,由数据库厂商去开发具体的实现。

4.jdbc开发步骤

1.导入数据库驱动jar包
2.注册驱动程序
3.获取数据库连接
4.创建Statement对象
5.使用Statement对象来发送sql语句
6.得到结果,处理结果
7.关闭连接

5.mysql使用

//1.导入数据库驱动jar包
// mysql-connector-java-5.1.7-bin.jar
//2.注册驱动程序
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接
String url = "jdbc:mysql://localhost:3306/test";
String username="root";
String password="root";
Connection connection = DriverManager.getConnection(url, username, password);
//4.创建Statement对象
Statement statement = connection.createStatement();
//5.使用Statement对象来发送sql语句
String sql = "create table student(id int primary key auto_increment,name varchar(20),age int)";
int count = statement.executeUpdate(sql);
//6.得到结果,处理结果
System.out.println("影响了"+count+"行");
//7.关闭连接
statement.close();
connection.close();

5.jdbc的详细体系

1.jdbc接口存放位置:java.sql.*  javax.sql.*
2.核心接口
    DriverManager
        用于加载驱动,并创建与数据库的链接
        注意:在实际开发中并不推荐采用registerDriver方法注册驱动。原因有二:
            一、查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次,也就是在内存中会有两个Driver对象。
            二、程序依赖mysql的api,脱离mysql的jar包,程序将无法编译,将来程序切换底层数据库将会非常麻烦。
        推荐方式:Class.forName(“com.mysql.jdbc.Driver”);
            采用此种方式不会导致驱动对象在内存中重复出现,并且采用此种方式,程序仅仅只需要一个字符串,不需要依赖具体的驱动,使程序的灵活性更高。
    Driver接口 : 驱动程序接口,使用驱动程序可以连接数据库
        如果DriverManager.registerDriver(new com.mysql.jdbc.Driver());这样写等于注册两次。看源码里面有静态代码块。
        所以 Class.forName("com.mysql.jdbc.Driver");就是在加载Driver中的静态代码块,这样就等于注册了。
    Connection接口 : 代表一个数据库连接
        它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。
        Connection的使用原则是尽量晚创建,尽量早的释放。
        为确保资源释放代码能运行,资源释放代码也一定要放在finally语句中。
    Statement接口 : 用来执行静态sql语句
    PreparedStatement接口 : 用来执行预编译sql语句
    CallableStatement接口 : 用来执行存储过程的sql语句
    ResultSet接口 : 代表数据库的结果集
        Jdbc程序中的ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。
        ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next() 方法,
        可以使游标指向具体的数据行,进行调用方法获取该行的数据。

6.常用数据类型转换表

image.png

7.增加

//1.导入数据库驱动jar包
// mysql-connector-java-5.1.7-bin.jar
//2.注册驱动程序
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接
String url = "jdbc:mysql://localhost:3306/test";
String username="root";
String password="root";
Connection connection = DriverManager.getConnection(url, username, password);
//4.创建Statement对象
Statement statement = connection.createStatement();
//5.使用Statement对象来发送sql语句
String sql = "insert into student(name,age) values ('张三',20)";
int count = statement.executeUpdate(sql);
//6.得到结果,处理结果
System.out.println("影响了"+count+"行");
//7.关闭连接
statement.close();
connection.close();

8.修改

//1.导入数据库驱动jar包
// mysql-connector-java-5.1.7-bin.jar
//2.注册驱动程序
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接
String url = "jdbc:mysql://localhost:3306/test";
String username="root";
String password="root";
Connection connection = DriverManager.getConnection(url, username, password);
//4.创建Statement对象
Statement statement = connection.createStatement();
//5.使用Statement对象来发送sql语句
String sql = "update student set age=30 where id = 1";
int count = statement.executeUpdate(sql);
//6.得到结果,处理结果
System.out.println("影响了"+count+"行");
//7.关闭连接
statement.close();
connection.close();

9.删除

//1.导入数据库驱动jar包
// mysql-connector-java-5.1.7-bin.jar
//2.注册驱动程序
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接
String url = "jdbc:mysql://localhost:3306/test";
String username="root";
String password="root";
Connection connection = DriverManager.getConnection(url, username, password);
//4.创建Statement对象
Statement statement = connection.createStatement();
//5.使用Statement对象来发送sql语句
String sql = "delete from student where id = 1";
int count = statement.executeUpdate(sql);
//6.得到结果,处理结果
System.out.println("影响了"+count+"行");
//7.关闭连接
statement.close();
connection.close();

10.查询

//1.导入数据库驱动jar包
// mysql-connector-java-5.1.7-bin.jar
//2.注册驱动程序
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接
String url = "jdbc:mysql://localhost:3306/test";
String username="root";
String password="root";
Connection connection = DriverManager.getConnection(url, username, password);
//4.创建Statement对象
Statement statement = connection.createStatement();
//5.使用Statement对象来发送sql语句
String sql = "select * from student";
//6.得到结果,处理结果
ResultSet resultSet = statement.executeQuery(sql);
while(resultSet.next()){
    int id = resultSet.getInt(1);
    String name = resultSet.getString(2);
    int age = resultSet.getInt(3);
    System.out.println(id+"-"+name+"-"+age);
}
//7.关闭连接
statement.close();
connection.close();

11.数据库分页

Select * from table limit M,N 
    M:记录开始索引位置,从0开始
    N:取多少条记录。

12.jdbc封装

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCUtil {
    private static final String url = "jdbc:mysql://localhost:3306/test";
    private static final String username = "root";
    private static final String password = "root";
    static{
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection(){
        try {
            Connection connection = DriverManager.getConnection(url, username, password);
            return connection;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    } 
    public static void close(ResultSet resultSet,Statement statement,Connection connection){
        if(resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

13.PreparedStatement预编译

//?代表一个参数的占位,预编译验证sql是否正确
PreparedStatement prepareStatement = connection.prepareStatement("insert into student(id,name,age) values(?,?,?)");
//给参数赋值
prepareStatement.setInt(1,1);
prepareStatement.setString(2,"shuaige");
prepareStatement.setInt(3, 30);
//把参数发送给数据库
int count = prepareStatement.executeUpdate();
System.out.println("影响了"+count+"行");

14.PreparedStatement和Statement的区别

语法:
    Statement对象只支持静态sql,而PreparedStatement即支持静态sql也支持预编译sql
执行效率
    PreparedStatement对象比Statement对象执行sql的效率会高。因为PreparedStatement可以利用数据库的sql缓存。
    
    Statement发送sql到数据库
        1)校验sql的语法,如果不合法就报错
        2)校验sql的执行权限,如果没有权限就报错。
        3)执行sql
            sql缓存区:先在sql缓存区查询是否已经执行过此sql,如果执行过,则取出对应的执行任务执行,
            如果没有执行过,则创建新的执行任务执行,把当前的sql放入缓存区。 
    PreparedStatement发送sql到数据库
        1)先发送sql到数据库
        2)发送占位的数据到数据库
        以后就只需要发送占位的数据到数据库就可以了。在mysql不明显,在Oracle才明显。
安全问题
    Statement对象相对不安全。因为可以被sql注入。
    PreparedStatement能够防止sql注入。
结论:尽量使用PreparedStatement。
    select * from users where name='shuaige' and password='1234';
    select * from users where 1=1;
    select * from users where name='随便写' or 1=1 --' and password='1234'

15.执行存储过程CallableStatement

//1.带有输入参数的存储过程
//支持预编译
CallableStatement callableStatement = connection.prepareCall("call pro_test(?)");
callableStatement.setInt(1, 1);
ResultSet resultSet = callableStatement.executeQuery();


//2.带有输出参数的存储过程
//支持预编译
CallableStatement callableStatement = connection.prepareCall("call pro_test1(?)");
//把某个参数注册为输出参数
callableStatement.registerOutParameter(1, java.sql.Types.VARCHAR);
//执行存储过程
callableStatement.executeQuery();
//取出输出参数的值
String text = callableStatement.getString(1);

16.jdbc核心类图

image.png

17.url写法

//程序连接数据库的时候用UTF-8编码
?useUnicode=true&characterEncoding=UTF-8

18.读取配置文件获取数据库配置信息

//1.jdbc.properties代码
url=jdbc:mysql://localhost:3306/test
username=root
password=root
driverclass=com.mysql.jdbc.Driver

//2.读取代码
private static  String url = null;
private static  String username = null;
private static  String password = null;
private static  String driverclass = null;
static{
    Properties prop = new Properties();
    //JDBCUtil.class.getResourceAsStream 获得的是当前文件所在的包的src根目录
    InputStream inputStream = JDBCUtil.class.getResourceAsStream("/jdbc.properteis");
    try {
        prop.load(inputStream);
        url = prop.getProperty("url");
        username = prop.getProperty("username");
        password = prop.getProperty("password");
        driverclass = prop.getProperty("driverclass");
        Class.forName(driverclass);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

19.jdbc批处理

1)作用
    一次发送多条sql语句,从而提高数据库执行sql的效率。
2)批处理
    Statement
        addBatch方法 :添加sql到缓存
        excuteBatch方法 :发送缓存的所有sql语句到数据库执行
        clearBatch方法 :清空缓存的所有sql语句
    PreparedStatement
        addBatch方法 : 把一组参数添加到缓存
        executeBatch方法 :发送缓存的所有参数到数据库执行
        clearBatch方法 :清空缓存的所有参数
3)需求
    使用Statement直接插入
    使用Statement+批处理插入
    使用PreparedStatement直接插入
    使用PreparedStatement+批处理插入
    
    结论:Oracle对PreparedStatement和批处理的支持比较好,但是mysql的支持并不明显。

20.jdbc获取自增长的值

//1.导入数据库驱动jar包
// mysql-connector-java-5.1.7-bin.jar
//2.注册驱动程序
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接
String url = "jdbc:mysql://localhost:3306/test";
String username="root";
String password="root";
Connection connection = DriverManager.getConnection(url, username, password);
//4.创建PreparedStatement
String sql = "insert into demp(name) values(?)";
PreparedStatement prepareStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
prepareStatement.setString(1,"shuaige");
prepareStatement.executeUpdate();
ResultSet resultSet = prepareStatement.getGeneratedKeys();
int deptId = 0;
if(resultSet.next()){
    deptId = resultSet.getInt(1);
}
sql = "insert into user(name,deptno) values(?,?)";
PreparedStatement prepareStatement2 = connection.prepareStatement(sql);
prepareStatement2.setString(1, "shuaige");
prepareStatement2.setInt(2,deptId);
prepareStatement2.executeUpdate();

21.jdbc处理文件(字符和字节)数据

1)分类
    字符文件:txt html xml
        mysql : tinytext text(64k) mediumtext(16M) longtext(4G)
        oracle : clob
    字节文件:二进制,图片,视频,zip。。
        mysql : tinyblob blob(64k) mediumblob(16M) longblob(4G)
        oracle : blob
2)写入字符文件的代码
    //1.导入数据库驱动jar包
    // mysql-connector-java-5.1.7-bin.jar
    //2.注册驱动程序
    Class.forName("com.mysql.jdbc.Driver");
    //3.获取数据库连接
    String url = "jdbc:mysql://localhost:3306/test";
    String username="root";
    String password="root";
    Connection connection = DriverManager.getConnection(url, username, password);
    //4.创建PreparedStatement
    String sql = "insert into article(name,content) values(?,?)";
    PreparedStatement prepareStatement = connection.prepareStatement(sql);
    File file = new File("D:\\test.txt");
    FileReader reader = new FileReader(file);
    prepareStatement.setString(1,file.getName());
    prepareStatement.setClob(2, reader);
    prepareStatement.executeUpdate();
3)读取字符文件的代码
    //1.导入数据库驱动jar包
    // mysql-connector-java-5.1.7-bin.jar
    //2.注册驱动程序
    Class.forName("com.mysql.jdbc.Driver");
    //3.获取数据库连接
    String url = "jdbc:mysql://localhost:3306/test";
    String username="root";
    String password="root";
    Connection connection = DriverManager.getConnection(url, username, password);
    //4.创建PreparedStatement
    String sql = "select name,content from article where id = 1";
    PreparedStatement prepareStatement = connection.prepareStatement(sql);
    ResultSet resultSet = prepareStatement.executeQuery();
    if(resultSet.next()){
        Clob clob = resultSet.getClob("content");
        Reader reader = clob.getCharacterStream();
        //写出到硬盘即可
    }
4)写入字节文件的代码
    //1.导入数据库驱动jar包
    // mysql-connector-java-5.1.7-bin.jar
    //2.注册驱动程序
    Class.forName("com.mysql.jdbc.Driver");
    //3.获取数据库连接
    String url = "jdbc:mysql://localhost:3306/test";
    String username="root";
    String password="root";
    Connection connection = DriverManager.getConnection(url, username, password);
    //4.创建PreparedStatement
    String sql = "insert into images(content) values(?)";
    PreparedStatement prepareStatement = connection.prepareStatement(sql);
    File file = new File("D:\\shuaige.png");
    FileInputStream input = new FileInputStream(file);
    prepareStatement.setBlob(1, input);
    prepareStatement.executeUpdate();
5)读取字节文件的代码
    //1.导入数据库驱动jar包
    // mysql-connector-java-5.1.7-bin.jar
    //2.注册驱动程序
    Class.forName("com.mysql.jdbc.Driver");
    //3.获取数据库连接
    String url = "jdbc:mysql://localhost:3306/test";
    String username="root";
    String password="root";
    Connection connection = DriverManager.getConnection(url, username, password);
    //4.创建PreparedStatement
    String sql = "select content from images where id = 1";
    PreparedStatement prepareStatement = connection.prepareStatement(sql);
    ResultSet resultSet = prepareStatement.executeQuery();
    if(resultSet.next()){
        Blob blob = resultSet.getBlob(1);
        InputStream inputStream = blob.getBinaryStream();
        //写出到硬盘
    }

22.jdbc时间处理

//1.导入数据库驱动jar包
// mysql-connector-java-5.1.7-bin.jar
//2.注册驱动程序
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接
String url = "jdbc:mysql://localhost:3306/test";
String username="root";
String password="root";
Connection connection = DriverManager.getConnection(url, username, password);
//4.创建PreparedStatement
String sql = "insert into timetest(logintime) values(?)";
PreparedStatement prepareStatement = connection.prepareStatement(sql);
prepareStatement.setDate(1, new java.sql.Date(new java.util.Date().getTime()));
prepareStatement.setTimestamp(1, new Timestamp(new java.util.Date().getTime()));
prepareStatement.executeUpdate();

23.jdbc事务

1)概念
    如果把(多条)sql语句看做是一个事务,那么这个事务里面的操作要么一起成功过,要么一起失败。
2)数据库的事务功能
    数据库的类型innoDB才能完全支持事务。
3)数据库的事务命令
    set autocommit=0; 开始事务(关闭自动提交事务的功能。默认值1,自动提交事务 0,关闭自动提交)
    commit 提交事务,一旦事务提交了,就不能回滚了。
    rollback 回滚事务。回滚到事务的开始位置。
3)sql命令
    set autocommit=0;
4)代码
    //1.导入数据库驱动jar包
    // mysql-connector-java-5.1.7-bin.jar
    //2.注册驱动程序
    Class.forName("com.mysql.jdbc.Driver");
    //3.获取数据库连接
    String url = "jdbc:mysql://localhost:3306/test";
    String username="root";
    String password="root";
    Connection connection = DriverManager.getConnection(url, username, password);
    //4.创建PreparedStatement 
    String sql = "update account set money = money-1000 where name='jack'";
    String sql1 = "update account set money = money+1000 where name='rose'";
    connection.setAutoCommit(false);
    PreparedStatement prepareStatement = connection.prepareStatement(sql);
    prepareStatement.executeUpdate();
    //报错
    PreparedStatement prepareStatement2 = connection.prepareStatement(sql1);
    prepareStatement2.executeUpdate();
    connection.commit();
    //异常中回滚 rollback();
5)事务使用的场景
    一个业务操作里面存在多条的更新类语句(DML语句)
6)事务的四个特征
    原子性 (A)Atomicity
        事务是一个不可分割的单元,要么一起成功,要么一起失败
    一致性 (C)Consistency
        事务必须使数据库从一个一致性状态变换到另外一个一致性状态
    隔离性 (I)Isolation
        多个事务并发操作之间应该要相互隔离的
            如果多个事务之间没有隔离,则会产生以下现象:
                1)脏读
                    一个事务读到了另一个事务未提交的更新数据
                2)不可重复读
                    一个事务读到了另一个事务已经更新的数据
                3)幻读
                    一个事务读到了另一个事务已经提交的插入数据
            数据库的4个隔离级别来防止以上现象
                serializable 最高级别 不支持脏读,不可重复读,幻读
                repeatable read不支持脏读,不可重复读.支持幻读(mysql默认)
                read committed 不支持脏读.支持不可重复读,幻读(Oracle默认)
                read uncommitted 最低级别 支持脏读,不可重复读,幻读
            mysql查看事务的隔离级别
                select @@global.tx_isolation;(全局)
                select @@tx_isolation;(会话)
            mysql修改事务的隔离级别
                set global transaction isolation level read committed;(全局)
                set session transaction isolation level uncommitted;(会话)
                
            数据库隔离级别越高,安全性越高,数据库并发性能越低。
    持久性 (D)Durability
        事务一旦提交,数据永远保存下来

24.连接优化

为什么需要连接池
    访问前需要先获取连接
    每次操作结束后,要释放资源
    频繁的连接导致系统的安全性和稳定性差
工作原理
    连接池是由容器提供的,用来管理池中连接对象
    连接池自动分配连接对象并对闲置的连接进行回收

1)作用
    1.提高Connection对象的利用率(一个Connection对象可以多次操作)
    2.控制Connection对象的数量,保证数据库服务器的安全
    
    引入连接池的概念来实现以上的功能
2)连接池
    sun设计了一个接口 javax.sql.DataSource 是所有连接池实现的接口
        getConnection 从连接池中获取连接的方法
        

25.jdbc优化-自定义连接池

public class MyDataSource implements DataSource {
    
    public static LinkedList<Connection> pool = new LinkedList<Connection>();
    
    private int initCount = 5;
    private int maxCount = 10;
    private int currentCount=0;
    
    private static String url = "jdbc:mysql://localhost:3306/test";
    private static String user = "root";
    private static String password = "root";
    private static String driverClass = "com.mysql.jdbc.Driver";
    
    static{
        try {
            Class.forName(driverClass);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    private static Connection createConnection(){
        try {
            return DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
        }
        return null;
    }
    
    public MyDataSource() {
        for(int i = 0;i<initCount;i++){
            pool.add(createConnection());
            currentCount++;
        }
    }

    @Override
    public Connection getConnection() throws SQLException {
        if(pool.size()>0){
            //从池中取出并且返回一个连接
            return pool.removeFirst();
        }else{
            if(currentCount<maxCount){
                currentCount++;
                return createConnection();
            }
            throw new RuntimeException("超出最大连接数");
        }
    }

    public static void release(Connection conn){
        pool.addLast(conn);
    }
}

26.jdbc优化-自定义连接池优化-代理

代理模式的要数
    代理接口:代理类和被代理类共同的行为
    被代理类:需要被代理的类
    代理类:和被代理类有相同的接口,代理类可以有自己的实现

静态代理和动态代理

27.jdbc优化-自定义连接池优化-静态代理

特点是代理类是由开发者自行编写
步骤:
    1实现和被代理类相同的接口
    2声明一个接口成员变量
    3在代理类的构造方法中接收被代理类实例
    4在代理类的方法中实现自己的逻辑
    
处理自定义连接池的close方法

public class MyDataSource implements DataSource {
    public static LinkedList<Connection> pool = new LinkedList<Connection>();
    private int initCount = 5;
    private int maxCount = 10;
    private int currentCount=0;
    private static String url = "jdbc:mysql://localhost:3306/test";
    private static String user = "root";
    private static String password = "root";
    private static String driverClass = "com.mysql.jdbc.Driver";
    static{
        try {
            Class.forName(driverClass);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    private Connection createConnection(){
        try {
            Connection connection = DriverManager.getConnection(url, user, password);
            Connection proxy = new MyConnectionProxy(connection, this);
            return proxy;
        } catch (SQLException e) {
        }
        return null;
    }
    public MyDataSource() {
        for(int i = 0;i<initCount;i++){
            pool.add(createConnection());
            currentCount++;
        }
    }
    @Override
    public Connection getConnection() throws SQLException {
        if(pool.size()>0){
            //从池中取出并且返回一个连接
            return pool.removeFirst();
        }else{
            if(currentCount<maxCount){
                currentCount++;
                return createConnection();
            }
            throw new RuntimeException("超出最大连接数");
        }
    }
    public static void release(Connection conn){
        pool.addLast(conn);
    }
}

public class MyConnectionProxy implements Connection{
    private Connection realConn;
    private MyDataSource datasource;
    public MyConnectionProxy(Connection realConn,MyDataSource datasource){
        this.realConn = realConn;
        this.datasource = datasource;
    }
    @Override
    public Statement createStatement() throws SQLException {
        return realConn.createStatement();
    }
    @Override
    public void close() throws SQLException {
        datasource.pool.addLast(this);
    }
}

28.jdbc优化-自定义连接池优化-动态代理

特点是代理类是由程序生成的
public class MyDataSource implements DataSource {
    public static LinkedList<Connection> pool = new LinkedList<Connection>();
    private int initCount = 5;
    private int maxCount = 10;
    private int currentCount=0;
    private static String url = "jdbc:mysql://localhost:3306/test";
    private static String user = "root";
    private static String password = "root";
    private static String driverClass = "com.mysql.jdbc.Driver";
    static{
        try {
            Class.forName(driverClass);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    private Connection createConnection(){
        try {
            final Connection connection = DriverManager.getConnection(url, user, password);
            Connection proxy = (Connection)Proxy.newProxyInstance(this.getClass().getClassLoader(), 
                    new Class[]{Connection.class}, 
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object newproxy, Method method, Object[] args) throws Throwable {
                            String name = method.getName();
                            if("close".equals(name)){
                                pool.addLast((Connection)newproxy);
                            }else{
                                return method.invoke(connection, args);
                            }
                            return null;
                        }
                    });
            return proxy;
        } catch (SQLException e) {
        }
        return null;
    }
    public MyDataSource() {
        for(int i = 0;i<initCount;i++){
            pool.add(createConnection());
            currentCount++;
        }
    }
    @Override
    public Connection getConnection() throws SQLException {
        if(pool.size()>0){
            //从池中取出并且返回一个连接
            return pool.removeFirst();
        }else{
            if(currentCount<maxCount){
                currentCount++;
                return createConnection();
            }
            throw new RuntimeException("超出最大连接数");
        }
    }
    public static void release(Connection conn){
        pool.addLast(conn);
    }
}

29.jdbc优化-dbcp连接池

1)概述
    DBCP是Apache组织的工具,DataBase Connection Pool
    是Apache上的一个 java连接池项目,也是 tomcat使用的连接池组件
    下载 : 
        http://commons.apache.org/proper/commons-dbcp/download_dbcp.cgi
        http://commons.apache.org/proper/commons-pool/download_pool.cgi
        http://commons.apache.org/proper/commons-logging/download_logging.cgi
2)使用步骤
    导入jar包
    创建连接池
    配置连接池参数
    获取连接
3)1.4版本代码一
//测试类
public class Test {
    public static void main(String[] args) throws Exception {
        for(int i = 0;i<11;i++){
            Connection connection = DBCPUtil.getConnection();
            System.out.println(connection);
            //释放连接
            connection.close();
        }
    }
}
//工具类
//commons-dbcp-1.4.jar/commons-pool-1.6.jar
public class DBCPUtil {
    private static String url = "jdbc:mysql://localhost:3306/test";
    private static String user = "root";
    private static String password = "root";
    private static String driverClass = "com.mysql.jdbc.Driver";
    public static BasicDataSource datasource =null;
    static {
        datasource = new BasicDataSource();
        datasource.setUrl(url);
        datasource.setUsername(user);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClass);
        // 连接池参数配置
        // 初始化连接数
        datasource.setInitialSize(5);
        // 最大连接数
        datasource.setMaxActive(10);
        // 当超过最大连接后,最长等待时间
        datasource.setMaxWait(3000);
    }
    public static Connection getConnection(){
        try {
            return datasource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
}
//1.4版本代码二
//dbcp.properties
url=jdbc:mysql://localhost:3306/test
username=root
password=root
driverClassName=com.mysql.jdbc.Driver
initialSize=5
maxActive=10
maxWait=3000
//commons-dbcp-1.4.jar/commons-pool-1.6.jar
public class DBCPUtil {
    public static BasicDataSource datasource =null;
    static {
        Properties prop = new Properties();
        InputStream inputStream = DBCPUtil.class.getResourceAsStream("/dbcp.properties");
        try {
            prop.load(inputStream);
            datasource = (BasicDataSource) BasicDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection(){
        try {
            return datasource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        for(int i = 0;i<11;i++){
            Connection connection = DBCPUtil.getConnection();
            System.out.println(connection);
            //释放连接
            connection.close();
        }
    }
}
4)2.2.1版本代码一
//dbcp.properties
url=jdbc:mysql://localhost:3306/test
username=root
password=root
driverClassName=com.mysql.jdbc.Driver
initialSize=5
maxActive=10
maxWait=3000
//commons-dbcp2-2.1.1.jar/commons-pool2-2.4.2.jar/commons-logging-1.2.jar
public class DBCPUtil {
    public static BasicDataSource datasource =null;
    static {
        Properties prop = new Properties();
        InputStream inputStream = DBCPUtil.class.getResourceAsStream("/dbcp.properties");
        try {
            prop.load(inputStream);
            datasource = (BasicDataSource) BasicDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection(){
        try {
            return datasource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        for(int i = 0;i<11;i++){
            Connection connection = DBCPUtil.getConnection();
            System.out.println(connection);
            //释放连接
            connection.close();
        }
    }
}
//2.2.1代码二
//commons-dbcp2-2.1.1.jar/commons-pool2-2.4.2.jar/commons-logging-1.2.jar
public class DBCPUtil {
    private static String url = "jdbc:mysql://localhost:3306/test";
    private static String user = "root";
    private static String password = "root";
    private static String driverClass = "com.mysql.jdbc.Driver";
    public static BasicDataSource datasource =null;
    static {
        try {
            datasource = new BasicDataSource();
            datasource.setUrl(url);
            datasource.setUsername(user);
            datasource.setPassword(password);
            datasource.setDriverClassName(driverClass);
            datasource.setInitialSize(5);
            datasource.setMaxTotal(10);
            datasource.setMaxWaitMillis(3000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection(){
        try {
            return datasource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        for(int i = 0;i<11;i++){
            Connection connection = DBCPUtil.getConnection();
            System.out.println(connection);
            //释放连接
            connection.close();
        }
    }
}

jdbc优化-c3p0连接池

1)概述
    目前应用广泛的连接池工具之一。hibernate框架推荐使用的连接池
    C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。
    c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。
    扩展这些操作可以有效的提升性能。
    目前使用它的开源项目有Hibernate,Spring等。
    c3p0有自动回收空闲连接功能
    下载
        https://sourceforge.net/projects/c3p0/
2)使用步骤
    导入jar包
    核心类 ComboPooledDataSource
3)代码一
//c3p0-0.9.5.2.jar/mchange-commons-java-0.2.11.jar
public class C3P0Util {
    private static String url = "jdbc:mysql://localhost:3306/test";
    private static String user = "root";
    private static String password = "root";
    private static String driverClass = "com.mysql.jdbc.Driver";
    private static ComboPooledDataSource ds = null;
    static {
        ds = new ComboPooledDataSource();
        ds.setJdbcUrl(url);
        ds.setUser(user);
        ds.setPassword(password);
        try {
            ds.setDriverClass(driverClass);
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
        ds.setInitialPoolSize(5);
        ds.setMaxPoolSize(10);
        ds.setCheckoutTimeout(3000);
    }
    public static Connection getConnection(){
        try {
            return ds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        for(int i = 0;i<11;i++){
            Connection connection = C3P0Util.getConnection();
            System.out.println(connection);
            connection.close();
        }
    }
}
4)代码二
// c3p0-config.xml
<c3p0-config>
    <!-- 默认配置 -->
    <default-config>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">10</property>
        <property name="checkoutTimeout">3000</property>
    </default-config>
    <named-config name="intergalactoApp">
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">10</property>
        <property name="checkoutTimeout">3000</property>
    </named-config>
</c3p0-config>
//c3p0-0.9.5.2.jar/mchange-commons-java-0.2.11.jar
public class C3P0Util {
    private static ComboPooledDataSource ds = null;
    static {
        //读取默认配置default-config
        ds = new ComboPooledDataSource();
        //读取配置 named-config = intergalactoApp
        //ds = new ComboPooledDataSource("intergalactoApp");
    }
    public static Connection getConnection(){
        try {
            return ds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        for(int i = 0;i<11;i++){
            Connection connection = C3P0Util.getConnection();
            System.out.println(connection);
            connection.close();
        }
    }
}
获取数据库元数据 DatabaseMetaData 得到数据库相关信息
    DatabaseMetaData dbmd = connection.getMetaData();
    System.out.println(dbmd.getDatabaseProductName());
    System.out.println(dbmd.getDatabaseMajorVersion());
    System.out.println(dbmd.getDatabaseMinorVersion());
    System.out.println(dbmd.getDriverName());
    System.out.println(dbmd.getDriverMajorVersion());
    System.out.println(dbmd.getDriverMinorVersion());
参数元数据  ParameterMetaData 得到参数相关信息
结果集元数据  ResultSetMetaData 得到结果集相关信息

jdbc优化-Proxool连接池

Proxool是一种Java数据库连接池技术。
是sourceforge下的一个开源项目,这个项目提供一个健壮、易用的连接池,
最为关键的是这个连接池提供监控的功能,方便易用,便于发现连接泄漏的情况

jdbc优化-druid连接池

Tomcat中配置数据源连接池

在Tomcat中配置数据源
    在Tomcat中的conf/context.xml配置中的<Context></Context>元素下添加
        <Resource name="jdbc/shuaige" auth="Container" type="javax.sql.DataSource"
            maxActive="100" maxIdle="30" maxWait="10000" username="root" 
            password="root" driverClassName="com.mysql.jdbc.Driver"
            url="jdbc:mysql://localhost:3306/shuaige" />
        解释
            name:数据源名称
            type:指定管理者及数据源的类型
            maxActive:最大连接数
            maxWait:最长等待时间
            url:数据库路径
在项目中读取数据源-JNDI读取数据源
    JNDI是Java命名与目录接口
    通过名称与对象的绑定实现资源获取
    代码:
        //初始化上下文
        Context cxt=new InitialContext();
        //获取与逻辑名相关联的数据源对象
        //通过Context接口的lookup()查找数据源
        //java:comp/env为JavaEE默认路径
        //jdbc/news为DataSource的名称
        DataSource ds=(DataSource)cxt.lookup("java:comp/env/jdbc/shuaige");
        conn=ds.getConnection();

DBUtils工具(jdbc的简单封装)

概述
    Apache组织的一个框架,是对jdbc简单的封装
下载
    http://commons.apache.org/proper/commons-dbutils/download_dbutils.cgi
使用步骤
    导入jar包
         使用c3p0连接池 c3p0-0.9.5.2.jar/mchange-commons-java-0.2.11.jar
         commons-dbutils-1.7.jar
    核心类
        QueryRunner
增加数据
    public static void insert() throws Exception{
        QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
        String sql = "insert into user (name,age) values(?,?)";
        Integer id = qr.insert(sql, new ResultSetHandler<Integer>() {
            @Override
            public Integer handle(ResultSet resultSet) throws SQLException {
                resultSet.next();
                return resultSet.getInt(1);
            }
        },new Object[]{"shuaige",32});
        System.out.println("返回的新id是:"+id);
    }
更新数据
    public static void update() throws Exception{
        QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
        String sql = "update user set name = ? where id = ?";
        int count = qr.update(sql, new Object[]{"shuaigege",25});
        System.out.println("影响的记录数:"+count);
    }
删除数据
    public static void delete() throws Exception{
        QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
        String sql = "delete from user where id = ?";
        int count = qr.execute(sql, new Object[]{1});
        System.out.println("影响的记录数:"+count);
    }
查询数据
    public static void query()throws Exception{
        QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
        String sql = "select * from user";
        List<User> list = (List<User>)qr.query(sql,new ResultSetHandler(){
            @Override
            public Object handle(ResultSet resultSet) throws SQLException {
                List<User> list = new ArrayList<User>();
                while(resultSet.next()){
                    int id = resultSet.getInt("id");
                    String name = resultSet.getString("name");
                    int age = resultSet.getInt("age");
                    User user = new User(id,name,age);
                    list.add(user);
                }
                return list;
            }
        });
        System.out.println(list);
    }
    //BeanListHandler: 把结果集封装成一个List(javabean)
    public static void query()throws Exception{
        QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
        String sql = "select * from user";
        List<User> list = qr.query(sql,new BeanListHandler(User.class));
        System.out.println(list);
    }
    //BeanHandler: 把结果集封装成一个javabean
    public static void query()throws Exception{
        QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
        String sql = "select * from user where id = ?";
        User user = qr.query(sql,new BeanHandler(User.class),new Object[]{2});
        System.out.println(user);
    }
    //ArrayHandler: 把结果集的第一行封装成一个对象数组
    public static void query()throws Exception{
        QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
        String sql = "select * from user where id = ?";
        Object[] objArr = qr.query(sql,new ArrayHandler(),new Object[]{2});
        for(Object obj : objArr){
            System.out.println(obj);
        }
    }
    //ArrayListHandler: 把结果集封装成一个List(对象数组)
    public static void query()throws Exception{
        QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
        String sql = "select * from user where id = ?";
        List<Object[]> list = qr.query(sql,new ArrayListHandler(),new Object[]{2});
        for(Object[] objArr : list){
            for(Object obj :objArr){
                System.out.print(obj);
            }
            System.out.println();
        }
    }
    //ScalarHandler: 处理聚合查询的结果集 count(*) max(xx) min(xxx)
    public static void query()throws Exception{
        QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
        String sql = "select count(*) from user";
        Long count = qr.query(sql,new ScalarHandler());
        System.out.println(count);
    }
控制事务
    public static void useTransaction() throws Exception{
        Connection connection = C3P0Util.getConnection();
        connection.setAutoCommit(false);
        QueryRunner qr = new QueryRunner();
        try {
            String sql = "delete from user where id = ?";
            int count = qr.execute(connection,sql, new Object[]{1});
            System.out.println("影响的记录数:"+count);
            int a = 1/0;
            String sql1 = "update user set name = ? where id = ?";
            int count1 = qr.update(connection,sql1, new Object[]{"shuaigege",25});
            System.out.println("影响的记录数:"+count1);
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
            connection.rollback();
        }finally {
            connection.close();
        }
    }

其它

持久化
    持久化是将程序中的数据在瞬时状态和持久状态间转换的机制
    操作:增加、删除、更新、查询
DAO
    Data Access Object(数据存取对象)
    实现对持久化数据的访问
    DAO起着转换器的作用,把实体类转换为数据库中的记录
    写法:
        接口/实现类/实体类/数据库工具类
        
分层开发
    特点:
        每一层都有自己的职责
        上一层不用关心下一层的实现细节,上一层通过下一层提供的对外接口来使用其功能
        上一层调用下一层的功能,下一层不能调用上一层功能
    好处:
        各层专注于自己功能的实现,便于提高质量
        便于分工协作,提高开发效率
        便于代码复用
        便于程序扩展
        便于提高开发质量、提高开发效率、便于代码复用、便于程序扩展、便于降低代码的耦合性。
        分层时应坚持封装性原则和顺序访问原则  
        不同层之间通过实体类传输数据

ORM

概述
    Object Relation Mapping
    对象关系映射
    R(数据库)     M          O(java)
       表        <->           类
      字段       <->          属性
    一条记录     <->        一个对象    
ORM实现
    hibernate/mybatis/自己代码实现    

双向关联(一对多)

create table customer(
    id int primary key auto_increment,
    name varchar(20)
);
create table order(
    id int primary key auto_increment,
    orderno varchar(20),
    c_id int,
    constraint order_customer_fk foreign key(c_id) references customer(id)
);
public class Customer{
    private int id;
    private String name;
    private List<Order> orders = new ArrayList<Order>();
}
public class Order{
    private int id;
    private String orderno;
    private Customer customer;
}
public void save(){
    //1.保存用户
    insert into customer(name) values(?)
    //2.如果用户有关联订单,订单也一起保存
    for(Order order : customer.getOrders(){
        insert into order (orderno,c_id) values(?,?)
    }
}
public void query(){
    //1.查询用户
    select * from customer where id = ?
    //2.查询关联订单
    select * from order where c_id = ?
}

双向关联(多对多)

create table teacher(
    id int primary key auto_increment,
    name varchar(20)
);
create table student(
    id int primary key auto_increment,
    name varchar(20)
);
create table teacher_student(
    t_id int,
    s_id int,
    constraint tea_stu_tea_fk foreign key(t_id) references teacher(id),
    constraint tea_stu_stu_fk foreign key(s_id) references student(id)
);
public class Teacher{
    private int id;
    private String name;
    private List<Student> students = new ArrayList<Student>();
}
public class Student{
    private int id;
    private String name;
    private List<Teacher> teachers = new ArrayList<Teacher>();
}
public void save(){
    //1.插入老师
    insert into teacher(name) values(?);
    //2.如果有学生也插入
    insert into student(name) values(?);
    //3.插入中间表
    insert into teacher_student(t_id,s_id) values(?,?)
}
public void query(){
    //1.查询老师
    select * from teacher where id = ?
    //2.关联查询
    select s.* from student s,teacher_student ts where s.id = ts.s_id and t_id=?
}

双向关联 一对一

create table person(
    id int primary key auto_increment,
    name varchar(20)
)
create table idcard(
    id int primary key auto_increment,
    idno varchar(18),
    p_id int unique,
    constraint idcard_person_fk foreign key(p_id) references person(id)
)
public class Person{
    private int id;
    private String name;
    private IdCard idCard;
}
public class IdCard{
    private int id;
    private String idno;
    private Person person;
}
public void save(){
    //插入公民表
    insert into person (name) values(?)
    //插入身份证表
    insert into idcard (idno,p_id) values(?,?)
}
public void query(){
    //查询公民表
    select * from person where id = ?
    //查询身份证表
    select * from idcard where p_id = ?
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 本文包括传统JDBC的缺点连接池原理自定义连接池开源数据库连接池DBCP连接池C3P0连接池Tomcat内置连接池...
    廖少少阅读 16,723评论 0 37
  • JDBC概述 在Java中,数据库存取技术可分为如下几类:JDBC直接访问数据库、JDO技术、第三方O/R工具,如...
    usopp阅读 3,527评论 3 75
  • 主要内容 定义Spring的数据访问支持 配置数据库资源 使用Spring提供的JDBC模板 写在前面:经过上一篇...
    程序熊大阅读 8,744评论 1 31
  • 1.安装vim yum install vim 2.将系统的vi alias到vim 编辑/etc/bashrc ...
    大李子biglee阅读 1,397评论 0 0
  • 偷懒了一天,看一篇文章,也没认真搞懂其中的方法。 Zhang L, Ding X, Lu Z, Jung H S,...
    wanion911阅读 556评论 1 50