数据库模块

这里写目录标题

  • 一.数据库设计
  • 确定实体之间的关系
  • 创建数据表
  • 编写实体类
  • 二.封装数据库操作
    • 封装DButil
    • 针对文件的增删查改操作进行一个封装
      • 初始化数据库
      • 插入文件
      • 查询文件
      • 删除文件

一.数据库设计

确定实体之间的关系

因为我们要做的是一个文件搜索功能,我们这里的实体,就是文件,接下来我们可以来建立起关系
确定一个实体:文件
然后确定实体的属性有哪些
数据库模块_第1张图片

创建数据表

根据上面的属性,我们编写sql语句,进行创建表的操作

create table if not exists file_meta(
    id int  INTEGER PRIMARY KEY,
    name varchar(50) not null,
    path varchar(512) not null,
    is_directory boolean not null,
    pinyin varchar(100) not null,
    pinyin_first varchar(100) not null,
    size BIGINT not null,
    last_modified timestamp not null
);

编写实体类

既然我们的数据库创建好了,我们就可以编写相对应的实体类了,这是为了后续我们进行交互方便而创建的实体类

@Data
public class FileMeta {
    private int id;
    private String name; //文件名
    private String path; // 文件所在路径
    private boolean isDirectory; // 是否为目录
    private String pingyin;      // 文件名的拼音
    private long size; // 纯粹的数字表示防止,单位为字节
    private long lastModified; //最后的修改时间,时间戳格式.

}

创建好标准的实体类之后,我们需要处理一些细节问题
1.pingyin这个字段的处理
2.size这个字段的处理
3.lastModified字段的处理
至于为什么要单独对这些字段做处理,下面我将娓娓道来
开始对pingyin这个字段进行处理,原因如下:
pingyin实际上分为两种情况,一种是全拼和全拼首字母简写.所以我们不能用简单的一个字段去表示.
所以我们干脆直接一点,直接使用两个方法,来提供不同的pingyin属性的展示,具体的方法如下:

  /**
     * 获取到中文全拼写
     * @param name
     * @return
     */
    public  String getPinyin(String name){
        return PinyinUtil.get(name,true);
        
    }

    /**
     * 获取到中文拼音首字母简写
     * @param name
     * @return
     */
    public  String getPinyinFirst(String name){
        return PinyinUtil.get(name,false);

    }

现在又来考虑文件大小的处理,至于为什么要处理,大家看到我记下来的描述,就明白了.
文件大小的分类如下:
微小(小于1KB,即1024字节)
小(小于1MB,即10241024字节)
中(小于1GB,即102410241024字节)
大(大于等于1GB,即102410241024
1024字节及以上)
因此我们就要根据实际的文件大小,文件数据合理化,并且带上一定的单位,不能只给出个数字
具体思路如下:
1.比较size的大小关系
2…根据实际的关系进行转换

具体代码如下:

   public String getSizeText(){
        //通过这个方法,把size进行合理的转化
        //熟悉单位 byte,kb,mb,Gb
        //主要看size大小
        /*
        如果size <1024 单位使用byte
        如果size >=1024并且 size <1024*1024 单位使用mb
        依次类推...
         */
        //这里的关键思路,1.比较size大小关系.2.根据实际的关系,进行关系转换
        //BigDecimal 这是精确表示小数的方法
        String[] util={"Byte","KB","MB","GB"};
        for (int level = 0; level < util.length; level++) {
            if (curSize<1024){
                //直接使用level的单位
                return String.format("%.2f"+util[level],new BigDecimal(curSize));
            }
            curSize /=1024;
        }
        //当单位升级到GB还是不够用,就直接使用GB
        return String.format("%.2f GB",new BigDecimal(curSize));
    }

最后我们就要处理.lastModified字段的处理.这是表示文件的时间的,但我们如果什么都不做的话,我们之后打印的时间就是一串很长的时间戳.
在这里插入图片描述
因此我们就要对这个时间戳进行格式化,转换成我们想要的东西.代码如下:

   public String getLastModifiedText(){
        DateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return dateFormat.format(last_Modified);
    }

二.封装数据库操作

封装DButil

这个类就是建立数据库的连接和回收

package dao;

import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ;
import org.sqlite.SQLiteDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 数据库的连接和释放资源的封装
 */
public class DBUtil {
    //使用单例模式提供datasource,
    //我们必须防止线程安全问题
    private static volatile DataSource dataSource=null;
    private static  DataSource getDataSource(){
        if (dataSource ==null) {
            synchronized (DBUtil.class) {
                if (dataSource == null) {
                    dataSource = new SQLiteDataSource();
                    ((SQLiteDataSource) dataSource).setUrl("jdbc:sqlite://d:/ITsoftware/sqlite-tools-win32-x86-3420000/test.db");
                }
            }
        }

        return dataSource;

    }
    public static Connection getConnection() throws SQLException {
        return getDataSource().getConnection();
    }
    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
        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();
            }
        }
    }

}

针对文件的增删查改操作进行一个封装

初始模板如下:

//通过这个类来封装对file_meta表的操作
public class FileDao {
    //1.初始化数据库(建表)
    public void initDB(){

    }
    //2.插入文件/目录到数据库中
    //这里提供一个批量插入的方法
    public void add(List<FileMeta> fileMetas){

    }
    //3.按照特定的关键草进行查询
    // 这个是在实现文件搜索功能的时候,必备的查询
    public List<FileMeta> searchByPattern(String pattern){
        return null;
    }
    //4.这个方法给定一个路径,查询这个路径对应的结果(这个路径下都有哪些文件)
    //  这个方法会在后续继续重新扫描,更新数据库的时候要用到
    public  List<FileMeta> searchByPath(String targetPath){
        return null;
    }
    //5.删除文件
    //发现某个文件已经从磁盘中删掉了,此时就需要把表里的文件进行更新.
    public  void delete(List<FileMeta> fileMetas){
        
    }
}

初始化数据库

这里就是我们创建表的一个操作。
基本的过程就是,我们要去读取.sql文件里面的sql语句,从而完成基本创建表工作。代码如下:

/**
     *  //1.初始化数据库(建表)
     */

    public void  initDB(){
        //必须先能读到SQL语句
        //根据SQL语句调用JDBC的操作
        Connection connection=null;
        Statement statement=null;
        try {
            connection= DBUtil.getConnection();
            String sqls[]=getInitSQL();
            for (String sql: sqls) {
                System.out.println("initDB sql: "+sql);
                statement.executeUpdate(sql);
            }
        }catch (SQLException e){
            e.printStackTrace();
        }finally {
            DBUtil.close(connection, (PreparedStatement) statement,null);
        }


    }


    /**
     * 从db.sql读取SQL语句
     * @return string[]数组
     * 	1、通过ClassLoader获取db.sql文件的输入流。
		2、用输入流构造InputStreamReader,编码为utf-8。
		3、循环读取输入流中的字符。read()方法返回int值但表示一个char。
		4、将读取的字符append到StringBuilder中。
		5、文件读取结束时,退出循环,并把StringBuilder中的字符串使用分号";"分割成字符串数组。
		6、返回字符串数组。
     */
    private String[] getInitSQL() {
//        使用StringBulider 来存储最终的结果
        StringBuilder stringBuilder=new StringBuilder();
        try(InputStream inputStream=FileDao.class.getClassLoader().getResourceAsStream("db.sql")){
            try(InputStreamReader reader=new InputStreamReader(inputStream,"utf-8")){
                while (true){
                    int ch=reader.read();
                    if (ch == -1){
                        //文件读取完毕
                        break;
                    }
                    stringBuilder.append((char)ch);
                }
            }
        }catch (IOException e){
            e.printStackTrace();
        }
        //最终用分好来切分若干个sql
        return stringBuilder.toString().split(";");
    }

插入文件

注意一点,这里我们要插入的文件不是一个文件,而是一组文件。就是批量操作。
我针对这里的插入文件,大概整理了思路。

可以针对每个File_Meta 分别进行插入,外层再套入循环
但是更好的做法,就是使用事务
因为使用事务批量之心,比上述每次执行一个,分多次执行,来的更高效

如何使用jdbc来操作事务呢?
1.先把连接的自动提交功能关闭.
默认情况下,jdbc中的Connection,每次执行一个execute系列方法都会产生一次和数据库的交互.
2.针对每个要执行的SQL,使用PreparedStatement提供的addBatch方法进行累计.
实际上,PreparedStatement里面是可以包含多个SQL的.所以要累计.
3.增加好了所有要执行的Batch之后,统一的进行executeBatch,执行所有的SQL
4.使用commit这样的方法,告诉数据库,执行完毕.
5.如果上述执行过程中,出现了异常,此时就可以使用rollback进行回滚.

具体的代码如下:

 /**
     * 批量插入文件操作
     * @param fileMetas
     */
    public void add(List<FileMeta> fileMetas){
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        try {
            connection=DBUtil.getConnection();
            //1.关闭连接自动提交功能
            connection.setAutoCommit(false);
            //2.进行批量构建数据
            String sql="insert into file_meta values(null,?,?,?,?,?,?,?)";
            preparedStatement=connection.prepareStatement(sql);
            for (FileMeta fileMeta: fileMetas) {
                preparedStatement.setString(1, fileMeta.getName());
                preparedStatement.setString(2,fileMeta.getPath());
                preparedStatement.setBoolean(3,fileMeta.isDirectory());
                preparedStatement.setString(4,fileMeta.getPinyin());
                preparedStatement.setString(5, fileMeta.getPinyinFirst());
                preparedStatement.setLong(6,fileMeta.getSize());
                preparedStatement.setTimestamp(7,new Timestamp(fileMeta.getLast_Modified()));
                //使用addBatch方法,把这个构造好的片段累计起来。
                preparedStatement.addBatch();
            }
            //3.执行所有的sql片段
            preparedStatement.executeBatch();
            //4.使用commit告诉数据库执行完毕
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
            //5,如果上述代码出现了异常就要进行回滚操作
            if (connection == null) {
                try {
                    connection.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
        }finally {
            DBUtil.close(connection,preparedStatement,null);
        }

    }

查询文件

查询文件的业务逻辑也是分情况的。分为以下两种情况
1.按关键字查询2.按路径查询
1.按关键字查询

接下来我就分为以下两种情况来展开讨论,大家也不用害怕,就是sql语句的不同而已
首先进入关键字查询,这是实现文件搜索功能必备的查询,输入的查询可能是文件的一部分,也有可能是文件拼音的一部分,也有可能是拼音首字母的一部分,所以我们要在sql语句上下功夫

具体步骤如下:
1.建立数据库连接。
2.构造模糊查询的SQL语句,根据name、pinyin、pinyin_first字段匹配。
3.用prepareStatement设置查询模式的参数。
4.执行查询,获取结果集。
5.循环结果集,将每行结果构造为FileMeta对象。
6.将FileMeta对象添加到列表中。
7.关闭数据库资源。
8.返回文件元数据列表。

 /**
     * 批量插入文件操作
     * @param fileMetas
     */
    public void add(List<FileMeta> fileMetas){
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        try {
            connection=DBUtil.getConnection();
            //1.关闭连接自动提交功能
            connection.setAutoCommit(false);
            //2.进行批量构建数据
            String sql="insert into file_meta values(null,?,?,?,?,?,?,?)";
            preparedStatement=connection.prepareStatement(sql);
            for (FileMeta fileMeta: fileMetas) {
                preparedStatement.setString(1, fileMeta.getName());
                preparedStatement.setString(2,fileMeta.getPath());
                preparedStatement.setBoolean(3,fileMeta.isDirectory());
                preparedStatement.setString(4,fileMeta.getPinyin());
                preparedStatement.setString(5, fileMeta.getPinyinFirst());
                preparedStatement.setLong(6,fileMeta.getSize());
                preparedStatement.setTimestamp(7,new Timestamp(fileMeta.getLast_Modified()));
                //使用addBatch方法,把这个构造好的片段累计起来。
                preparedStatement.addBatch();
            }
            //3.执行所有的sql片段
            preparedStatement.executeBatch();
            //4.使用commit告诉数据库执行完毕
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
            //5,如果上述代码出现了异常就要进行回滚操作
            if (connection == null) {
                try {
                    connection.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
        }finally {
            DBUtil.close(connection,preparedStatement,null);
        }

    }
    public List<FileMeta> searchByPattern(String pattern){
        List<FileMeta>FileMetaList=new ArrayList<>();
        //1.建立连接
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        ResultSet resultSet=null;
        try {
            connection=DBUtil.getConnection();
            //2.根据模糊查询构造sql语句
            String sql="select name ,path , is_directory,size,last_modified from file_meta"+
                    "where name like ? or pinyin like? or pinyin_first like ?"+
                    "order by path,name";
            preparedStatement=connection.prepareStatement(sql);
            //3.设置查询模式的参数
            preparedStatement.setString(1,"%"+pattern+"%");
            preparedStatement.setString(2,"%"+pattern+"%");
            preparedStatement.setString(3,"%"+pattern+"%");
            //4.循环结果集。将每一行结果都构造成FileMeta对象。
            resultSet=preparedStatement.executeQuery();
            while (resultSet.next()){
                String name=resultSet.getString("name");
                String path=resultSet.getString("path");
                Boolean isDirectory=resultSet.getBoolean("isDirectory");
                long size=resultSet.getLong("size");
                Timestamp lastModified=resultSet.getTimestamp("last_modified");
                FileMeta fileMeta=new FileMeta(name,path,isDirectory,size,lastModified.getTime());
                FileMetaList.add(fileMeta);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
        DBUtil.close(connection,preparedStatement,resultSet);
    }
        return FileMetaList;
    }

2.按路径查询
这个方法给定一个路径,查询这个路径对应的结果(这个路径下都有哪些文件),具体的方法思路如下:
1.建立数据库连接。
2.构造精确查询的SQL语句,根据path字段完全匹配。
3.用prepareStatement设置路径的参数。
4.执行查询,获取结果集。
5.循环结果集,将每行结果构造为FileMeta对象。
6.将FileMeta对象添加到列表中。
7.关闭数据库资源。
8.返回文件元数据列表。
代码如下:

  public List<FileMeta> searchByPattern(String pattern){
        List<FileMeta>FileMetaList=new ArrayList<>();
        //1.建立连接
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        ResultSet resultSet=null;
        try {
            connection=DBUtil.getConnection();
            //2.根据模糊查询构造sql语句
            String sql="select name ,path , is_directory,size,last_modified from file_meta"+
                    "where name like ? or pinyin like? or pinyin_first like ?"+
                    "order by path,name";
            preparedStatement=connection.prepareStatement(sql);
            //3.设置查询模式的参数
            preparedStatement.setString(1,"%"+pattern+"%");
            preparedStatement.setString(2,"%"+pattern+"%");
            preparedStatement.setString(3,"%"+pattern+"%");
            //4.循环结果集。将每一行结果都构造成FileMeta对象。
            resultSet=preparedStatement.executeQuery();
            while (resultSet.next()){
                String name=resultSet.getString("name");
                String path=resultSet.getString("path");
                Boolean isDirectory=resultSet.getBoolean("isDirectory");
                long size=resultSet.getLong("size");
                Timestamp lastModified=resultSet.getTimestamp("last_modified");
                FileMeta fileMeta=new FileMeta(name,path,isDirectory,size,lastModified.getTime());
                FileMetaList.add(fileMeta);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
        DBUtil.close(connection,preparedStatement,resultSet);
    }
        return FileMetaList;
    }
    public List<FileMeta> searchByPath(String targetPath){
        Connection connection=null;
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        List<FileMeta> fileMetaList=new ArrayList<>();
        try {
            //1.建立连接
            connection=DBUtil.getConnection();
            //2.构造sql
            String sql="select name,path,is_directory,last_modified from file" +
                    "where path=?";
            statement=connection.prepareStatement(sql);
            statement.setString(1,targetPath);
            resultSet=statement.executeQuery();
            while (resultSet.next()){
                String name=resultSet.getString("name");
                String path=resultSet.getString("path");
                Boolean isDirectory=resultSet.getBoolean("is_directory");
                long size=resultSet.getLong("size");
                Timestamp lastModified=resultSet.getTimestamp("last_modified");
                FileMeta fileMeta=new FileMeta(name,path,isDirectory,size,lastModified.getTime());
                fileMetaList.add(fileMeta);

            }


        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return fileMetaList;
    }

删除文件

这里的删除文件,与其说是删除,倒不如说是更新数据库里面的内容。因为我们我们这个是文件搜索工具,比如说我们计算机删除了一个文件或者说新增加一个,那么我们数据库就要更新。另外我们删除问价的话,不同的情况,我们也要讨论,具体的删除情况说明如下:
这里进行删除的时候,有两种情况
1.有可能是一个普通文件=>直接删除对应的表记录就行了
2.也有可能删除的是个目录=>此时就要把目录里包含的子文件/子目录统一删除掉

方法主要的逻辑如下:
1.获取数据库连接,关闭自动提交。
2.循环遍历每个FileMeta,根据是否目录,构造不同的删除SQL。
3.如果不是目录,则只根据name和path删除。如果是目录,还需要额外把路径like字句追加,以删除目录下所有子文件。
4。使用prepareStatement设置参数,执行删除。
5.删除语句不可批处理,需要逐条执行。
6.循环结束后,提交事务。
7.出现异常,回滚事务。
8.最后关闭数据库连接。
代码如下:

  /**
     * 删除文件
     * @param fileMetas
     */
    public  void delete(List<FileMeta> fileMetas){
        Connection connection=null;
        PreparedStatement statement=null;
        try {
            //1.关闭事物自动提交

            connection=DBUtil.getConnection();
            connection.setAutoCommit(false);
            //2.分为不同的情况删除
            for (FileMeta fileMeta:fileMetas){
                String sql="";
                //情况1:不是目录
                if (!fileMeta.isDirectory()){
                    sql="delete from file_meta where name=? and path=?";
                }else {
                //情况2:是目录
                    //例如,当前要删除的path是 d://test
                    //此处path like ? 要替换成形容:'d://test%'
                    sql="delete from file_meta where(name=? and path=?) or (path like ?)";
                }
                //此处不能像前面的aad一样,使用addBatch,addBatch的前提是,sql是一个模板
                //把 ? 替换成不同的值.此处的sql不一定是相同的.
                //此处需要重新构造出statement了
                statement=connection.prepareStatement(sql);
                if (!fileMeta.isDirectory()){
                    //针对普通文件的替换
                    statement.setString(1,fileMeta.getName());
                    statement.setString(2,fileMeta.getPath());
                }else {
                    //针对目录的替换,需要替换三个问号
                    statement.setString(1,fileMeta.getName());
                    statement.setString(2,fileMeta.getPath());
                    statement.setString(3,fileMeta.getPath() + File.separator+fileMeta.getName()+"%");

                }

            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

其中:

File.separator: 是文件路径分隔符,在Windows下是"“,在Linux下是”/“。
fileMeta.getName(): 获取当前FileMeta的文件名。
“%”: SQL LIKE模糊匹配的通配符。
整体意思是:
构造一个以当前文件名作为开头,后面加上路径分隔符,再加上%作为通配符的字符串。
这种字符串可以用在SQL语句的LIKE条件中,来匹配当前目录下面所有的子文件。
例如,如果当前文件名为"test”,整个字符串就是:“test%”。这样就可以匹配到路径为"d:\test"下面的所有子文件。
所以这行代码的作用就是递归删除当前目录下的所有子文件数据,实现目录的整体删除。

你可能感兴趣的:(实战项目,java,sql,tomcat)