这里写目录标题
- 一.数据库设计
- 确定实体之间的关系
- 创建数据表
- 编写实体类
- 二.封装数据库操作
- 封装DButil
- 针对文件的增删查改操作进行一个封装
- 初始化数据库
- 插入文件
- 查询文件
- 删除文件
一.数据库设计
确定实体之间的关系
因为我们要做的是一个文件搜索功能,我们这里的实体,就是文件,接下来我们可以来建立起关系
确定一个实体:文件
然后确定实体的属性有哪些
创建数据表
根据上面的属性,我们编写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,即1024102410241024字节及以上)
因此我们就要根据实际的文件大小,文件数据合理化,并且带上一定的单位,不能只给出个数字
具体思路如下:
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"下面的所有子文件。
所以这行代码的作用就是递归删除当前目录下的所有子文件数据,实现目录的整体删除。