使用JUnit进行单元测试

!!!!!!
前排提醒:我们要将pom.xml中的junit版本改成4.12及以上的!
!!!!!!

首先一问:我们为什么需要单元测试?

随着我们的项目逐渐变大,比如我们之前编写的图书管理系统,我们都是边在写边在测试,而我们当时使用的测试方法,就是直接在主方法中运行测试,但是,在很多情况下,我们的项目可能会很庞大,不可能每次都去完整地启动一个项目测试某一个功能,这样显然会降低我们的开发效率,因此,我们需要使用单元测试来帮助我们针对于某个功能或是某个模块单独运行代码进行测试,而不是启动整个项目。

同时,在我们项目的维护过程中,难免会涉及到一些原有代码的修改,很有可能出现改了代码导致之前的功能出现问题(牵一发而动全身),而我们又不一定能立即察觉到,因此,我们可以提前保存一些测试用例,每次完成代码后都可以跑一遍测试用例,来确保之前的功能没有因为后续的修改而出现问题。

我们还可以利用单元测试来评估某个模块或是功能的耗时和性能,快速排查导致程序运行缓慢的问题,这些都可以通过单元测试来完成,可见单元测试对于开发的重要性。

1、尝试JUnit

首先需要导入JUnit依赖,我们在这里使用Junit4进行介绍,最新的Junit5(相比Junit4改动略大)放到Maven板块一起讲解,Jar包已经放在视频下方简介中,直接去下载即可。同时IDEA需要安装JUnit插件(默认是已经捆绑安装的,因此无需多余配置)

<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope>
</dependency>

(注意如果是jar包,除了jnuit包本身,还有一个hamcrest-core包)

现在我们创建一个新的类 ,来编写我们的单元测试用例:
(方法中不能有任何参数!)
(然后方法前就会出现一个绿色的小按钮,表示这个方法是可以直接运行的)
(然后点击后,下面出现一个专用于测试的窗口)
(也可以直接点击这个测试类左边的按钮,就可以一次性执行整个测试用例)

public class TestMain {@Testpublic void method(){System.out.println("我是测试用例1");}@Testpublic void method2(){System.out.println("我是测试用例2");}
}

我们可以点击类前面的测试按钮,或是单个方法前的测试按钮,如果点击类前面的测试按钮,会执行所有的测试用例。

运行测试后,我们发现控制台得到了一个测试结果,显示为绿色表示测试通过

只需要通过打上@Test注解,即可将一个方法标记为测试案例,我们可以直接运行此测试案例,但是我们编写的测试方法有以下要求:

  • 方法必须是public
  • 不能是静态方法
  • 返回值必须是void
  • 必须是没有任何参数的方法
  • 对于一个测试案例来说,我们肯定希望测试的结果是我们所期望的一个值,因此,如果测试的结果并不是我们所期望的结果,那么这个测试就应该没有成功通过!

我们可以通过断言工具类来进行判定:

public class TestMain {@Testpublic void method(){System.out.println("我是测试案例!");Assert.assertEquals(1, 2);    //参数1是期盼值,参数2是实际测试结果值}
}



(如果把2改成1就通过了)

通过运行代码后,我们发现测试过程中抛出了一个错误,并且IDEA给我们显示了期盼结果和测试结果,那么现在我们来测试一个案例,比如我们想查看冒泡排序的编写是否正确:

@Test
public void method(){int[] arr = {0, 4, 5, 2, 6, 9, 3, 1, 7, 8};//错误的冒泡排序for (int i = 0; i < arr.length - 1; i++) {for (int j = 0; j < arr.length - 1 - i; j++) {if(arr[j] > arr[j + 1]){int tmp = arr[j];arr[j] = arr[j+1];// arr[j+1] = tmp;}}}System.out.println(Arrays.toString(arr));Assert.assertArrayEquals(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, arr);
}

通过测试,我们发现得到的结果并不是我们想要的结果,因此现在我们需要去修改为正确的冒泡排序,修改后,测试就能正确通过了。我们还可以再通过一个案例来更加深入地了解测试,现在我们想测试从数据库中取数据是否为我们预期的数据:

(注意我们这里为什么能直接比较student是否相等呢?因为Student类上有一个@Data注解包含了@EqualsAndHashCode

@Test
public void method(){try (SqlSession sqlSession = MybatisUtil.getSession(true)){TestMapper mapper = sqlSession.getMapper(TestMapper.class);Student student = mapper.getStudentBySidAndSex(1, "男");Assert.assertEquals(new Student().setName("小明").setSex("男").setSid(1), student);}
}

那么如果我们在进行所有的测试之前需要做一些前置操作该怎么办呢,一种办法是在所有的测试用例前面都加上前置操作,但是这样显然是很冗余的,因为一旦发生修改就需要挨个进行修改,因此我们需要更加智能的方法,我们可以通过@Before注解来添加测试用例开始之前的前置操作
(每个测试方法前都会再执行一次)
(这与junit5中不同,junit5中给细分了,不会每个测试前都执行一次)

public class TestMain {private SqlSessionFactory sqlSessionFactory;@Beforepublic void before(){System.out.println("测试前置正在初始化...");try {sqlSessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream("mybatis-config.xml"));} catch (FileNotFoundException e) {e.printStackTrace();}System.out.println("测试初始化完成,正在开始测试案例...");}@Testpublic void method1(){try (SqlSession sqlSession = sqlSessionFactory.openSession(true)){TestMapper mapper = sqlSession.getMapper(TestMapper.class);Student student = mapper.getStudentBySidAndSex(1, "男");Assert.assertEquals(new Student().setName("小明").setSex("男").setSid(1), student);System.out.println("测试用例1通过!");}}@Testpublic void method2(){try (SqlSession sqlSession = sqlSessionFactory.openSession(true)){TestMapper mapper = sqlSession.getMapper(TestMapper.class);Student student = mapper.getStudentBySidAndSex(2, "女");Assert.assertEquals(new Student().setName("小红").setSex("女").setSid(2), student);System.out.println("测试用例2通过!");}}
}

同理,在所有的测试完成之后,我们还想添加一个收尾的动作,那么只需要使用@After注解即可添加结束动作:

@After
public void after(){System.out.println("测试结束,收尾工作正在进行...");
}

有关JUnit的使用我们就暂时只介绍这么多。

JUL日志系统

首先一问:我们为什么需要日志系统?

我们之前一直都在使用System.out.println来打印信息,但是,如果项目中存在大量的控制台输出语句,会显得很凌乱,而且日志的粒度是不够细的,假如我们现在希望,项目只在debug的情况下打印某些日志,而在实际运行不打印日志,采用直接输出的方式就很难实现了,因此我们需要使用日志框架规范化日志输出

而JDK为我们提供了一个自带的日志框架,位于java.util.logging包下,我们可以使用此框架来实现日志的规范化打印,使用起来非常简单:

(这里获取Logger的方式类似于 单例模式 的写法)

public class Main {public static void main(String[] args) {// 首先获取日志打印器Logger logger = Logger.getLogger(Main.class.getName());// 调用info来输出一个普通的信息,直接填写字符串即可logger.info("我是普通的日志");}
}

我们可以在主类中使用日志打印,得到日志的打印结果:

我们发现,通过日志输出的结果会更加规范。

1、JUL日志讲解

日志分为7个级别,详细信息我们可以在Level类(java.util.logging.Level)中查看:

  • SEVERE(最高值)- 一般用于代表严重错误
  • WARNING - 一般用于表示某些警告,但是不足以判断为错误
  • INFO默认级别) - 常规消息
  • CONFIG
  • FINE
  • FINER
  • FINEST(最低值)

我们之前通过info方法直接输出的结果就是使用的默认级别的日志,我们可以通过log方法来设定该条日志的输出级别

public static void main(String[] args) {Logger logger = Logger.getLogger(Main.class.getName());logger.log(Level.SEVERE, "严重的错误", new IOException("我就是错误"));logger.log(Level.WARNING, "警告的内容");logger.log(Level.INFO, "普通的信息");logger.log(Level.CONFIG, "级别低于普通信息");
}

我们发现,级别低于默认级别的日志信息,无法输出到控制台,我们可以通过设置来修改日志的打印级别:
(注意logger.setLevel和handler.setLevel要一起改才可以,如果其中一个不放行,也不能出来的)
(将logger.setLevel和handler.setLevel中的Level.CONFIG改成Level.ALL以后,那些等级比默认级别低的也可以被输出到控制台了!!)

public static void main(String[] args) {Logger logger = Logger.getLogger(Main.class.getName());//修改日志级别// 注意这个不是枚举,而是静态常量logger.setLevel(Level.CONFIG); //不使用父日志处理器,(所以现在没有任何处理器了)logger.setUseParentHandlers(false);//使用自定义日志处理器,(又有处理器了)ConsoleHandler handler = new ConsoleHandler();handler.setLevel(Level.CONFIG);logger.addHandler(handler);logger.log(Level.SEVERE, "严重的错误", new IOException("我就是错误"));logger.log(Level.WARNING, "警告的内容");logger.log(Level.INFO, "普通的信息");logger.log(Level.CONFIG, "级别低于普通信息");
}

每个Logger都有一个父日志打印器,我们可以通过getParent()来获取:

public static void main(String[] args) throws IOException {Logger logger = Logger.getLogger(Main.class.getName());System.out.println(logger.getParent().getClass());
}

我们发现,得到的是java.util.logging.LogManager$RootLogger这个类,它默认使用的是ConsoleHandler,且日志级别为INFO,由于每一个日志打印器都会直接使用父类的处理器,因此我们之前需要关闭父类然后使用我们自己的处理器

我们通过使用自己日志处理器来自定义级别的信息打印到控制台,当然,日志处理器不仅仅只有控制台打印,我们也可以使用文件处理器来处理日志信息,我们继续添加一个处理器

//添加输出到本地文件
FileHandler fileHandler = new FileHandler("test.log");
fileHandler.setLevel(Level.WARNING);
logger.addHandler(fileHandler);

因此,变成了:

public class Main {public static void main(String[] args) throws IOException {Logger logger = Logger.getLogger(Main.class.getName());//修改日志级别// 注意这个不是枚举,而是静态常量logger.setLevel(Level.ALL);//不使用父日志处理器,(所以现在没有任何处理器了)logger.setUseParentHandlers(false);//使用自定义日志处理器,(又有处理器了)ConsoleHandler handler = new ConsoleHandler();handler.setLevel(Level.ALL);logger.addHandler(handler);//添加输出到本地文件FileHandler fileHandler = new FileHandler("test.log");fileHandler.setLevel(Level.WARNING); // 因此WARNING以下的等级不会被写入!!!logger.addHandler(fileHandler);logger.log(Level.SEVERE, "严重的错误", new IOException("我就是错误"));logger.log(Level.WARNING, "警告的内容");logger.log(Level.INFO, "普通的信息");logger.log(Level.CONFIG, "级别低于普通信息");}
}

(然后会在IDEA中生成test.log文件)

打开test.log:

注意,这个时候就有两个日志处理器了,因此控制台和文件的都会生效。如果日志的打印格式我们不喜欢,我们还可以自定义打印格式,比如我们控制台处理器默认使用的是SimpleFormatter,而文件处理器则是使用的XMLFormatter,我们可以自定义:

//使用自定义日志处理器(控制台)
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.CONFIG);
handler.setFormatter(new XMLFormatter());
logger.addHandler(handler);

控制台:

public class Main {public static void main(String[] args) throws IOException {Logger logger = Logger.getLogger(Main.class.getName());logger.setLevel(Level.ALL);logger.setUseParentHandlers(false);SimpleFormatter formatter = new SimpleFormatter();ConsoleHandler handler = new ConsoleHandler();handler.setLevel(Level.ALL);logger.addHandler(handler);FileHandler fileHandler = new FileHandler("test.log");fileHandler.setLevel(Level.WARNING);fileHandler.setFormatter(formatter);logger.addHandler(fileHandler);logger.log(Level.SEVERE, "严重的错误", new IOException("我就是错误"));logger.log(Level.WARNING, "警告的内容");logger.log(Level.INFO, "普通的信息");logger.log(Level.CONFIG, "级别低于普通信息");}
}

test.log:

但以上每次(?)运行会把原先的覆盖,我们只要在FileHandler fileHandler = new FileHandler("test.log");中加一个参数true就可以在尾部添加而不是覆盖,因此变成FileHandler fileHandler = new FileHandler("test.log", true);

我们可以直接配置为想要的打印格式,如果这些格式还不能满足你,那么我们也可以自行实现:

public static void main(String[] args) throws IOException {Logger logger = Logger.getLogger(Main.class.getName());logger.setUseParentHandlers(false);//为了让颜色变回普通的颜色,通过代码块在初始化时将输出流设定为System.out(因为这个是protected方法,因此只能以这种方式更改,在构造的时候代码块中改)ConsoleHandler handler = new ConsoleHandler(){{setOutputStream(System.out);}};//创建匿名内部类实现自定义的格式(Formatter是一个抽象类)handler.setFormatter(new Formatter() {@Overridepublic String format(LogRecord record) {SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");String time = format.format(new Date(record.getMillis()));  //格式化日志时间String level = record.getLevel().getName();  // 获取日志级别名称// String level = record.getLevel().getLocalizedName();   // 获取本地化名称(语言跟随系统)String thread = String.format("%10s", Thread.currentThread().getName());  //线程名称(做了格式化处理,留出10格空间)long threadID = record.getThreadID();   //线程IDString className = String.format("%-20s", record.getSourceClassName());  //发送日志的类名String msg = record.getMessage();   //日志消息//\033[33m作为颜色代码,30~37都有对应的颜色,38是没有颜色,IDEA能显示,但是某些地方可能不支持return "\033[38m" + time + "  \033[33m" + level + " \033[35m" + threadID+ "\033[38m --- [" + thread + "] \033[36m" + className + "\033[38m : " + msg + "\n";}});logger.addHandler(handler);logger.info("我是测试消息1...");logger.log(Level.INFO, "我是测试消息2...");logger.log(Level.WARNING, "我是测试消息3...");
}

日志可以设置过滤器,如果我们不希望某些日志信息被输出,我们可以配置过滤规则:

public static void main(String[] args) throws IOException {Logger logger = Logger.getLogger(Main.class.getName());//自定义过滤规则(接口Filter,lambda表达式缩写new)// contains还可以换成equals等// 这样的话,那些 不包含“普通”的可以被输出logger.setFilter(record -> !record.getMessage().contains("普通"));logger.log(Level.SEVERE, "严重的错误", new IOException("我就是错误"));logger.log(Level.WARNING, "警告的内容");logger.log(Level.INFO, "普通的信息");
}

实际上,整个日志的输出流程如下:

2、Properties配置文件

Properties文件是Java的一种配置文件,我们之前学习了XML,但是我们发现XML配置文件读取实在是太麻烦,那么能否有一种简单一点的配置文件呢?我们可以使用Properties文件:

(创建 test.properties 文件,图标自动会变)(在根目录下创建)

name=Test
desc=Description

该文件配置很简单,格式为配置项=配置值,我们可以直接通过Properties类来将其读取为一个类似于Map一样的对象:

public static void main(String[] args) throws IOException {Properties properties = new Properties();properties.load(new FileInputStream("test.properties"));System.out.println(properties);
}
{name=Test, desc=Description}
// 与Map一样拥有 get 方法2
System.out.println(properties.get("name"));
// Test

(HashMap支持插入空值,但是properties(继承自Hashtable)不支持空值)

public class Main {public static void main(String[] args) throws IOException {Properties properties = new Properties();// 成功Map<String, String> map = new HashMap<>();map.put("kkk", null);// 失败(异常)properties.load(new FileInputStream("test.properties"));properties.put("kkk", null);System.out.println(properties);}
}

我们发现,Properties类是继承自Hashtable(过时),而Hashtable是实现的Map接口,也就是说,Properties本质上就是一个Map一样的结构,它会把所有的配置项映射为一个Map,这样我们就可以快速地读取对应配置的值了。

我们也可以将已经存在的Properties对象放入输出流进行保存,我们这里就不保存文件了,而是直接打印到控制台,我们只需要提供输出流即可:

public static void main(String[] args) throws IOException {Properties properties = new Properties();// properties.setProperty("test", "lbwnb");  //和put效果一样properties.put("test", "lbwnb");properties.store(System.out, "Test Message");//properties.storeToXML(System.out, "????");  保存为XML格式
}

(#表示注释)

我们可以通过System.getProperties()获取系统的参数,我们来看看:

public static void main(String[] args) throws IOException {System.getProperties().store(System.out, "系统信息:");
}

3、编写日志配置文件

我们可以通过进行配置文件来规定日志打印器的一些默认值

(写在test.properties中)

# RootLogger 的默认处理器为
handlers= java.util.logging.ConsoleHandler
# RootLogger 的默认的日志级别
.level= INFO

我们来尝试使用配置文件来进行配置:

public static void main(String[] args) throws IOException {//获取日志管理器// 因为这里也不能直接new(protected),因此也只能像logger一样获取静态方法LogManager manager = LogManager.getLogManager();//读取我们自己的配置文件manager.readConfiguration(new FileInputStream("test.properties"));//再获取日志打印器Logger logger = Logger.getLogger(Main.class.getName());logger.log(Level.INFO, "我是一条日志信息");   //通过自定义配置文件,我们发现默认级别不再是INFO了
}

我们也可以去修改ConsoleHandler默认配置

# 指定默认日志级别
java.util.logging.ConsoleHandler.level = ALL
# 指定默认日志消息格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定默认的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8

其实,我们阅读ConsoleHandler的源码就会发现,它就是通过读取配置文件来进行某些参数设置:

// Private method to configure a ConsoleHandler from LogManager
// properties and/or default values as specified in the class
// javadoc.
private void configure() {LogManager manager = LogManager.getLogManager();String cname = getClass().getName();setLevel(manager.getLevelProperty(cname +".level", Level.INFO));setFilter(manager.getFilterProperty(cname +".filter", null));setFormatter(manager.getFormatterProperty(cname +".formatter", new SimpleFormatter()));try {setEncoding(manager.getStringProperty(cname +".encoding", null));} catch (Exception ex) {try {setEncoding(null);} catch (Exception ex2) {// doing a setEncoding with null should always work.// assert false;}}
}

4、使用Lombok快速开启日志

我们发现,如果我们现在需要全面使用日志系统,而不是传统的直接打印,那么就需要在每个类都去编写获取Logger的代码,这样显然是很冗余的,能否简化一下这个流程呢?

前面我们学习了Lombok,我们也体会到Lombok给我们带来的便捷,我们可以通过一个注解快速生成构造方法、Getter和Setter,同样的,Logger也是可以使用Lombok快速生成的。

@Log
public class Main {public static void main(String[] args) {System.out.println("自动生成的Logger名称:"+log.getName());log.info("我是日志信息");}
}

只需要添加一个@Log注解即可,添加后,我们可以直接使用一个静态变量log,而它就是自动生成的Logger。我们也可以手动指定名称

@Log(topic = "打工是不可能打工的")
public class Main {public static void main(String[] args) {System.out.println("自动生成的Logger名称:"+log.getName());log.info("我是日志信息");}
}

5、Mybatis日志系统

Mybatis也有日志系统,它详细记录了所有的数据库操作等,但是我们在前面的学习中没有开启它,现在我们学习了日志之后,我们就可以尝试开启Mybatis的日志系统,来监控所有的数据库操作,要开启日志系统,我们需要进行配置
(添加在mybatis-config.xml中configuration下的settings中)

<setting name="logImpl" value="STDOUT_LOGGING" />

logImpl包括很多种配置项,包括 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING,而默认情况下是未配置,也就是说不打印。我们这里将其设定为STDOUT_LOGGING表示直接使用标准输出日志信息打印到控制台,我们编写一个测试案例来看看效果:

public class TestMain {private SqlSessionFactory sqlSessionFactory;@Beforepublic void before(){try {sqlSessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream("mybatis-config.xml"));} catch (FileNotFoundException e) {e.printStackTrace();}}@Testpublic void test(){try(SqlSession sqlSession = sqlSessionFactory.openSession(true)){TestMapper mapper = sqlSession.getMapper(TestMapper.class);System.out.println(mapper.getStudentBySidAndSex(1, "男"));System.out.println(mapper.getStudentBySidAndSex(1, "男"));}}
}

我们发现,两次获取学生信息,只有第一次打开了数据库连接,而第二次并没有。

现在我们学习了日志系统,那么我们来尝试使用日志系统输出Mybatis的日志信息
(还是在mybatis-config.xml中更改)

<setting name="logImpl" value="JDK_LOGGING" />

将其配置为JDK_LOGGING表示使用JUL进行日志打印,因为Mybatis的日志级别都比较低,因此我们需要设置一下logging.properties默认的日志级别:
(test.properties中)

handlers= java.util.logging.ConsoleHandler
.level= ALL
java.util.logging.ConsoleHandler.level = ALL

代码编写如下:

@Log
public class TestMain {private SqlSessionFactory sqlSessionFactory;@Beforepublic void before(){try {sqlSessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream("mybatis-config.xml"));// 首先要让 LogManager 读取配置,不然配置不生效的LogManager manager = LogManager.getLogManager();manager.readConfiguration(new FileInputStream("test.properties"));} catch (IOException e) {e.printStackTrace();}}@Testpublic void test(){try(SqlSession sqlSession = sqlSessionFactory.openSession(true)){TestMapper mapper = sqlSession.getMapper(TestMapper.class);log.info(mapper.getStudentBySidAndSex(1, "男").toString());log.info(mapper.getStudentBySidAndSex(1, "男").toString());}}
}

但是我们发现,这样的日志信息根本没法看,因此我们需要修改一下日志的打印格式,我们自己创建一个格式化类
(也在test下和TestMain同级!)
(Formatter是 java.util.logging包!)

public class TestFormatter extends Formatter {@Overridepublic String format(LogRecord record) {SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");String time = format.format(new Date(record.getMillis()));  //格式化日志时间return time + " : " + record.getMessage() + "\n";}
}

现在再来修改一下默认的格式化实现

(注意修改包类名,这里是对应了这种情况下正确)

handlers= java.util.logging.ConsoleHandler
.level= ALL
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = com.test.TestFormatter

现在就好看多了,当然,我们还可以继续为Mybatis添加文件日志,这里就不做演示了。

使用Maven管理项目

开始之前,看看你C盘空间够不够,最好预留2GB空间以上!

Maven 翻译为"专家"、“内行”,是 Apache 下的一个纯 Java 开发的开源项目。基于项目对象模型(缩写:POM)概念,Maven利用一个中央信息片断能管理一个项目的构建、报告和文档等步骤。Maven 是一个项目管理工具,可以对 Java 项目进行构建、依赖管理。Maven 也可被用于构建和管理各种项目,例如 C#,Ruby,Scala 和其他语言编写的项目。Maven 曾是 Jakarta 项目的子项目,现为由 Apache 软件基金会主持的独立 Apache 项目。

通过Maven,可以帮助我们做:

  • 项目的自动构建,包括代码的编译、测试、打包、安装、部署等操作。
  • 依赖管理,项目使用到哪些依赖,可以快速完成导入。

我们之前并没有讲解如何将我们的项目打包为Jar文件运行,同时,我们导入依赖的时候,每次都要去下载对应的Jar包,这样其实是很麻烦的,并且还有可能一个Jar包依赖于另一个Jar包,就像之前使用JUnit一样,因此我们需要一个更加方便的包管理机制。

Maven也需要安装环境,但是IDEA已经自带了Maven环境,因此我们不需要再去进行额外的环境安装(无IDEA也能使用Maven,但是配置过程很麻烦,并且我们现在使用的都是IDEA的集成开发环境,所以这里就不讲解Maven命令行操作了)我们直接创建一个新的Maven项目即可。

(新建一个项目,选择“maven模块

0、惯用配置

pom.xml

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>LearnMaven</artifactId><packaging>jar</packaging><version>1.0-SNAPSHOT</version><name>LearnMaven</name><!-- FIXME change it to the project's website --><url>http://www.example.com</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.1</version><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version><scope>provided</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.7</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><!-- JUnit 5 requires Surefire version 2.22.0 or higher --><version>2.22.0</version></plugin><plugin><artifactId>maven-assembly-plugin</artifactId><version>3.1.0</version><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive><manifest><addClasspath>true</addClasspath><mainClass>com.test.Main</mainClass></manifest></archive></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions></plugin></plugins><pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --><plugins><!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle --><plugin><artifactId>maven-clean-plugin</artifactId><version>3.1.0</version></plugin><!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging --><plugin><artifactId>maven-resources-plugin</artifactId><version>3.0.2</version></plugin><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.8.0</version></plugin><plugin><artifactId>maven-surefire-plugin</artifactId><version>2.22.1</version></plugin><plugin><artifactId>maven-jar-plugin</artifactId><version>3.0.2</version></plugin><plugin><artifactId>maven-install-plugin</artifactId><version>2.5.2</version></plugin><plugin><artifactId>maven-deploy-plugin</artifactId><version>2.8.2</version></plugin><!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle --><plugin><artifactId>maven-site-plugin</artifactId><version>3.7.1</version></plugin><plugin><artifactId>maven-project-info-reports-plugin</artifactId><version>3.0.0</version></plugin></plugins></pluginManagement></build>
</project>

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><settings><setting name="mapUnderscoreToCamelCase" value="true"/><setting name="cacheEnabled" value="true"/><setting name="logImpl" value="STDOUT_LOGGING" /></settings><!-- 需要在environments的上方 --><typeAliases><package name="com.test.entity"/></typeAliases><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/qwq"/><property name="username" value="root"/><property name="password" value="root123456"/></dataSource></environment></environments><mappers><mapper class="com.test.mapper.TestMapper"/></mappers>
</configuration>

1、Maven项目结构

我们可以来看一下,一个Maven项目和我们普通的项目有什么区别:


resources中一般就是存一些静态资源,比如配置文件

那么首先,我们需要了解一下POM文件,它相当于是我们整个Maven项目配置文件,它也是使用XML编写的:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>MavenTest</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties></project>

我们可以看到,Maven的配置文件是以project根节点,而modelVersion定义了当前模型的版本,一般是4.0.0,我们不用去修改。

groupIdartifactIdversion这三个元素合在一起,用于唯一区别每个项目,别人如果需要将我们编写的代码作为依赖,那么就必须通过这三个元素来定位我们的项目,我们称为一个项目的基本坐标,所有的项目一般都有自己的Maven坐标,因此我们通过Maven导入其他的依赖只需要填写这三个基本元素就可以了,无需再下载Jar文件,而是Maven自动帮助我们下载依赖并导入。

groupId 一般用于指定组名称,命名规则一般和包名一致,比如我们这里使用的是org.example一个组下面可以有很多个项目
artifactId 一般用于指定项目当前组中的唯一名称,也就是说在组中用于区分于其他项目的标记。
version 代表项目版本,随着我们项目的开发和改进,版本号也会不断更新,就像LOL一样,每次赛季更新都会有一个大版本更新,我们的Maven项目也是这样,我们可以手动指定当前项目的版本号,其他人使用我们的项目作为依赖时,也可以根本版本号进行选择(这里的SNAPSHOT代表快照,一般表示这是一个处于开发中的项目,正式发布项目一般只带版本号
properties中一般都是一些变量和选项的配置,我们这里指定了JDK源代码编译版本为1.8,无需进行修改。

2、Maven依赖导入

现在我们尝试使用Maven来帮助我们快速导入依赖,我们需要导入之前的JDBC驱动依赖、JUnit依赖、Mybatis依赖、Lombok依赖,那么如何使用Maven来管理依赖呢?

我们可以创建一个dependencies节点:

<dependencies>//里面填写的就是所有的依赖
</dependencies>

那么现在就可以向节点中填写依赖了,那么我们如何知道每个依赖的坐标呢?我们可以在:https://mvnrepository.com/ 进行查询(可能打不开,建议用流量,或是直接百度某个项目的Maven依赖),我们直接搜索lombok即可,打开后可以看到已经给我们写出了依赖的坐标:

<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version><scope>provided</scope>
</dependency>

我们直接将其添加到dependencies节点中即可,现在我们来编写一个测试用例看看依赖导入成功了没有:

public class Main {public static void main(String[] args) {Student student = new Student("小明", 18);System.out.println(student);}
}

(com.test.entity.Student)

@Data
@AllArgsConstructor
public class Student {int sid;String name;String sex;
}

项目运行成功,表示成功导入了依赖。那么,Maven是如何进行依赖管理呢,以致于如此便捷的导入依赖,我们来看看Maven项目的依赖管理流程

通过流程图我们得知,一个项目依赖一般是存储在中央仓库中,也有可能存储在一些其他的远程仓库(私服),几乎所有的依赖都被放到了中央仓库中,因此,Maven可以直接从中央仓库中下载大部分的依赖(Maven第一次导入依赖是需要联网的),远程仓库中下载之后 ,会暂时存储在本地仓库,我们会发现我们本地存在一个.m2文件夹,这就是Maven本地仓库文件夹,默认建立在C盘,如果你C盘空间不足,会出现问题!

在下次导入依赖时,如果Maven发现本地仓库中就已经存在某个依赖,那么就不会再去远程仓库下载了。

可能在导入依赖时,小小伙伴们会出现卡顿的问题,我们建议配置一下IDEA自带的Maven插件远程仓库地址,我们打开IDEA的安装目录,找到安装根目录/plugins/maven/lib/maven3/conf文件夹,找到settings.xml文件,打开编辑:

找到mirros标签,添加以下内容:

<mirror><id>nexus-aliyun</id><mirrorOf>*</mirrorOf><name>Nexus aliyun</name><url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>

这样,我们就将默认远程仓库地址国外),配置为国内的阿里云仓库地址了(依赖的下载速度就会快起来了)

3、Maven依赖作用域

除了三个基本的属性用于定位坐标外,依赖还可以添加以下属性:

  • type:依赖的类型,对于项目坐标定义的packaging。大部分情况下,该元素不必声明,其默认值为jar
  • scope:依赖的范围(作用域,着重讲解)
  • optional:标记依赖是否可选
  • exclusions:用来排除传递性依赖(一个项目有可能依赖于其他项目,就像我们的项目,如果别人要用我们的项目作为依赖,那么就需要一起下载我们项目的依赖,如Lombok)

我们着重来讲解一下scope属性,它决定了依赖的作用域范围:

  • compile:为默认的依赖有效范围。如果在定义依赖关系的时候,没有明确指定依赖有效范围的话,则默认采用该依赖有效范围。此种依赖,在编译、运行、测试时均有效。
  • provided :在编译、测试时有效,但是在运行时无效,也就是说,项目在运行时,不需要此依赖,比如我们上面的Lombok,我们只需要在编译阶段使用它,编译完成后,实际上已经转换为对应的代码了,因此Lombok不需要在项目运行时也存在。
  • runtime :在运行、测试时有效,但是在编译代码时无效。比如我们如果需要自己写一个JDBC实现,那么肯定要用到JDK为我们指定的接口,但是实际上在运行时是不用自带JDK的依赖,因此只保留我们自己写的内容即可。(用的都是JDK的东西,因此不需要将它打包到jar中)
  • test :只在测试时有效,例如:JUnit,我们一般只会在测试阶段使用JUnit,而实际项目运行时,我们就用不到测试了,那么我们来看看,导入JUnit的依赖:

同样的,我们可以在网站上搜索Junit的依赖,我们这里导入最新的JUnit5作为依赖:

<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.1</version><scope>test</scope>
</dependency>

我们所有的测试用例全部编写到Maven项目给我们划分的test目录下,位于此目录下的内容不会在最后被打包到项目中,只用作开发阶段测试使用:

com.test.MainTest(一般以 test 结尾)
(注意是 org.junit.jupiter.api.Test

@Log
public class MainTest {@Testpublic void test(){log.info("测试");//Assert在JUnit5时名称发生了变化AssertionsAssertions.assertArrayEquals(new int[]{1, 2, 3}, new int[]{1, 2});}
}

因此,一般仅用作测试的依赖JUnit只保留在测试中即可,那么现在我们再来添加JDBC和Mybatis的依赖:
(由于JDBC和Mybatis在项目运行时也是需要的!因此给一个默认作用域即可;如果项目运行时库缺少,会找不到类)

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version>
</dependency>
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.7</version>
</dependency>

我们发现,Maven还给我们提供了一个resource文件夹,我们可以将一些静态资源,比如配置文件,放入到这个文件夹中,项目在打包时会将资源文件夹中文件一起打包到Jar中,比如我们在这里编写一个Mybatis的配置文件:
(resourcesmybatis-config.xml
(java -> com.test.mapper.TestMapper
(前面还有Student)
(注意我们在mybatis中打开了日志!!!)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><settings><setting name="mapUnderscoreToCamelCase" value="true"/><setting name="cacheEnabled" value="true"/><setting name="logImpl" value="STDOUT_LOGGING" /></settings><!-- 需要在environments的上方 --><typeAliases><package name="com.test.entity"/></typeAliases><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/qwq"/><property name="username" value="root"/><property name="password" value="root123456"/></dataSource></environment></environments><mappers><mapper class="com.test.mapper.TestMapper"/></mappers>
</configuration>
public interface TestMapper {@Select("select * from student where sid = #{sid}")Student getStudentBySid(int sid);
}

(由于现在配置文件是在jar包内部,不在外部,所以我们这里建立工厂直接用mybatis提供的Resources.get…)

public class Main {public static void main(String[] args) throws IOException {SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));try (SqlSession session = factory.openSession(true)) {TestMapper mapper = session.getMapper(TestMapper.class);System.out.println(mapper.getStudentBySid(1));}}
}

现在我们创建一下测试用例,顺便带大家了解一下Junit5的一些比较方便的地方:

public class MainTest {//因为配置文件位于内部,我们需要使用Resources类的getResourceAsStream来获取内部的资源文件private static SqlSessionFactory factory;//在JUnit5中@Before被废弃,它被细分了:@BeforeAll // 一次性开启所有测试案例只会执行一次 (方法必须是static)// @BeforeEach 一次性开启所有测试案例每个案例开始之前都会执行一次@SneakyThrowspublic static void before(){factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));}@DisplayName("Mybatis数据库测试")  //自定义测试名称@RepeatedTest(3)  //自动执行多次测试public void test(){try (SqlSession sqlSession = factory.openSession(true)){TestMapper testMapper = sqlSession.getMapper(TestMapper.class);System.out.println(testMapper.getStudentBySid(1));}}
}

那么就有人提问了,如果我需要的依赖没有上传的远程仓库,而是只有一个Jar怎么办呢?我们可以使用第四种作用域:

system:作用域和provided是一样的,但是它不是从远程仓库获取,而是直接导入本地Jar包

<dependency><groupId>javax.jntm</groupId><artifactId>lbwnb</artifactId><version>2.0</version><scope>system</scope><systemPath>C://学习资料/4K高清无码/test.jar</systemPath>
</dependency>

比如上面的例子,如果scope为system,那么我们需要添加一个systemPath来指定jar文件的位置,这里就不再演示了。

4、Maven可选依赖

当项目中的某些依赖不希望被使用此项目作为依赖的项目使用时,我们可以给依赖添加optional标签表示此依赖是可选的,默认在导入依赖时,不会导入可选的依赖:

<optional>true</optional>

比如MybatisPOM文件中,就存在大量的可选依赖:
(scope为 test 的也不会导入)

<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.30</version><optional>true</optional>
</dependency>
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.30</version><optional>true</optional>
</dependency>
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version><optional>true</optional>
</dependency>...

由于Mybatis要支持多种类型的日志,需要用到很多种不同的日志框架,因此需要导入这些依赖来做兼容,但是我们项目中并不一定会使用这些日志框架作为Mybatis的日志打印器,因此这些日志框架仅Mybatis内部做兼容需要导入使用,而我们可以选择不使用这些框架或是选择其中一个即可,也就是说我们导入Mybatis之后想用什么日志框架再自己加就可以了。

5、Maven排除依赖

我们了解了可选依赖,现在我们可以让使用此项目作为依赖的项目默认不使用可选依赖,但是如果存在那种不是可选依赖,但是我们导入此项目有不希望使用此依赖该怎么办呢,这个时候我们就可以通过排除依赖来防止添加不必要的依赖:

<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.1</version><scope>test</scope><exclusions><exclusion><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId></exclusion></exclusions>
</dependency>

我们这里演示了排除JUnit的一些依赖,我们可以在外部库中观察排除依赖之后和之前的效果。

6、Maven继承关系

一个Maven项目可以继承自另一个Maven项目,比如多个子项目都需要父项目的依赖,我们就可以使用继承关系来快速配置。

我们右键左侧栏,新建一个模块,来创建一个子项目:

在父项目的 pom.xml下多了:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>MavenTest</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>ChildModel</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties></project>

我们可以看到,IDEA默认给我们添加了一个parent节点,表示此Maven项目是父Maven项目的子项目,子项目直接继承父项目的groupId,子项目会直接继承父项目的所有依赖除非依赖添加了optional标签,我们来编写一个测试用例尝试一下:

import lombok.extern.java.Log;@Log
public class Main {public static void main(String[] args) {log.info("我是日志信息");}
}

可以看到,子项目也成功继承了Lombok依赖。

我们还可以让父Maven项目统一管理所有的依赖,包括版本号等,子项目可以选取需要的作为依赖,而版本全由父项目管理,我们可以将dependencies全部放入dependencyManagement节点,这样父项目就完全作为依赖统一管理

(注意左边会有相应的图标显示)

<dependencyManagement><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version><scope>provided</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.1</version><scope>test</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.7</version></dependency></dependencies>
</dependencyManagement>

我们发现,子项目的依赖失效了,因为现在父项目没有依赖,而是将所有的依赖进行集中管理,子项目需要什么再拿什么即可,同时子项目无需指定版本,所有的版本全部由父项目决定,子项目只需要使用即可:

(子项目中:)

<dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency>
</dependencies>

当然,父项目如果还存在dependencies(指的是不在dependencyManagement里面的dependencies,而是和dependencyManagement并列的dependencies)节点的话,里面的内依赖依然是直接继承

<dependencies><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.1</version><scope>test</scope></dependency>
</dependencies><dependencyManagement><dependencies>...

注意还有子项目之间也可以相互引用依赖!

7、Maven常用命令

我们可以看到在IDEA右上角Maven板块中,每个Maven项目都有一个生命周期,实际上这些是Maven的一些插件,每个插件都有各自的功能,比如:

(双击命令即可执行)

  • clean命令,执行后会清理整个target文件夹,在之后编写Springboot项目时可以解决一些缓存没更新的问题。
  • validate命令可以验证项目的可用性。
  • compile命令可以将项目编译为.class文件。(点击后又生成了target文件夹)
  • install命令可以将当前项目安装到本地仓库,以供其他项目导入作为依赖使用(点击后,就可以在/usr/local/repo/org/example/下找到这个项目了)
  • verify命令可以按顺序执行每个默认生命周期阶段(validate,compile,package等)

8、Maven测试项目

通过使用test命令,可以一键测试所有位于test目录下的测试案例,请注意有以下要求:

  • 测试类的名称必须是以Test结尾,比如MainTest
  • 测试方法上必须标注@Test注解,实测@RepeatedTest无效

这是由于JUnit5比较新,我们需要重新配置插件升级到高版本,才能完美的兼容Junit5

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><!-- JUnit 5 requires Surefire version 2.22.0 or higher --><version>2.22.0</version></plugin></plugins>
</build>

现在@RepeatedTest@BeforeAll也能使用了。

9、Maven打包项目

我们的项目在编写完成之后,要么作为Jar依赖,供其他模型使用,要么就作为一个可以执行的程序,在控制台运行,我们只需要直接执行package命令就可以直接对项目的代码进行打包生成jar文件


(我们可以在访达中查看(将打开方式换成MacZip)这个jar)

当然,以上方式仅适用于作为Jar依赖的情况,如果我们需要打包一个可执行文件,那么我不仅需要将自己编写的类打包到Jar中,同时还需要将依赖也一并打包到Jar中,因为我们使用了别人为我们通过的框架,自然也需要运行别人的代码,我们需要使用另一个插件来实现一起打包:(而默认的打包插件不支持这个)

(注意我们要在 <mainClass>com.test.Main</mainClass> 中配置主类!!!)

<plugin><artifactId>maven-assembly-plugin</artifactId><version>3.1.0</version><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive><manifest><addClasspath>true</addClasspath><mainClass>com.test.Main</mainClass></manifest></archive></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions>
</plugin>

(加上之后再双击package命令:)
(发现又多了一个以-jar-with-dependencies.jar结尾的jar包)

查看发现:

(除了com.test我们自己的包以外,mysql和mybatis的一些依赖和本身也进去了,这样的话我们这个程序才能跑得起来,不然找不到类)
(它会根据scope决定是否一起打包进jar,比如junit和lombok没有被打包进去)

在打包之前也会执行一次test命令,来保证项目能够正常运行,当测试出现问题时,打包将无法完成,我们也可以手动跳过,选择执行Maven目标来手动执行Maven命令,输入mvn package -Dmaven.test.skip=true来以跳过测试的方式进行打包。

最后得到我们的Jar文件,在同级目录下输入java -jar xxxx.jar(这个名称可以不自己输入,只要把桌面上的文件拖到cmd中即可显示名称)来运行我们打包好的Jar可执行程序(xxx代表文件名称)
(也就是说我们可以通过命令直接运行jar)

  • deploy命令用于发布项目到本地仓库和远程仓库(之前的install命令只能到本地仓库),一般情况下用不到,这里就不做讲解了。
  • site命令用于生成当前项目的发布站点,暂时不需要了解。

我们之前还讲解了多模块项目,那么多模块父项目存在一个packing打包类型标签,所有的父级项目的packing都为pompacking默认是jar类型,如果不作配置,maven会将该项目打成jar包。作为父级项目,还有一个重要的属性,那就是modules,通过modules标签将项目的所有子项目引用进来,在build父级项目时,会根据子模块的相互依赖关系整理一个build顺序,然后依次build。
(注意:maven打包方式有三种,pom,jar和war。当选择了打包方式(在pom.xml中的packaging标签中修改,默认为jar)为pom时,意味着该工程是个聚合工程,而这个工程也就是个父工程,只用来做依赖版本管理和模块管理,并不会涉及到源码,所以maven不会将resources下的文件编译到classes路径下。因此,会找不到mybatis-config.xml文件)

实战:基于Mybatis+JUL+Lombok+Maven的图书管理系统(带单元测试)

项目需求:

  • 在线录入学生信息和书籍信息 (数据库;两张表)
  • 查询书籍信息列表
  • 查询学生信息列表
  • 查询借阅信息列表 (所以还需要一张表存储学生和借阅的关系)
  • 完整的日志系统

0、完整项目

pom.xml

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>BookManager</artifactId><version>1.0-SNAPSHOT</version><name>BookManager</name><!-- FIXME change it to the project's website --><url>http://www.example.com</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.0.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version><scope>provided</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.1</version><scope>test</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.7</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.6.1</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.6.1</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><!-- JUnit 5 requires Surefire version 2.22.0 or higher --><version>2.22.0</version></plugin><plugin><artifactId>maven-assembly-plugin</artifactId><version>3.0.0</version><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive><manifest><addClasspath>true</addClasspath><mainClass>book.manage.Main</mainClass></manifest></archive></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions></plugin></plugins><pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --><plugins><!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle --><plugin><artifactId>maven-clean-plugin</artifactId><version>3.1.0</version></plugin><!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging --><plugin><artifactId>maven-resources-plugin</artifactId><version>3.0.2</version></plugin><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.8.0</version></plugin><plugin><artifactId>maven-surefire-plugin</artifactId><version>2.22.1</version></plugin><plugin><artifactId>maven-jar-plugin</artifactId><version>3.0.2</version></plugin><plugin><artifactId>maven-install-plugin</artifactId><version>2.5.2</version></plugin><plugin><artifactId>maven-deploy-plugin</artifactId><version>2.8.2</version></plugin><!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle --><plugin><artifactId>maven-site-plugin</artifactId><version>3.7.1</version></plugin><plugin><artifactId>maven-project-info-reports-plugin</artifactId><version>3.0.0</version></plugin></plugins></pluginManagement></build></project>

logging.properties

handlers= java.util.logging.FileHandler
.level= ALLjava.util.logging.FileHandler.pattern=console.log
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.append=true

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><settings><setting name="logImpl" value="NO_LOGGING"/></settings><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/book_manage"/><property name="username" value="root"/><property name="password" value="root123456"/></dataSource></environment></environments><mappers><mapper class="book.manage.mapper.BookMapper"/></mappers>
</configuration>

test/java/com.test.MainTest

public class MainTest {@Testpublic void test1() {SqlUtil.doSqlWork(mapper -> {mapper.getBorrowList().forEach(System.out::println);});}@Testpublic void test2() {SqlUtil.doSqlWork(mapper -> {mapper.getStudentList().forEach(System.out::println);});}@Testpublic void test3() {SqlUtil.doSqlWork(mapper -> {mapper.getBookList().forEach(System.out::println);});}
}

Main

@Log
public class Main {public static void main(String[] args) throws IOException {try (Scanner scanner = new Scanner(System.in)) {LogManager manager = LogManager.getLogManager();manager.readConfiguration(Resources.getResourceAsStream("logging.properties"));while (true) {System.out.println("========================");System.out.println("1. 录入学生信息");System.out.println("2. 录入书籍信息");System.out.println("3. 添加借阅信息");System.out.println("4. 查询借阅信息");System.out.println("5. 查询学生信息");System.out.println("6. 查询书籍信息");System.out.print("输入您想要执行的操作(输入其他任意数字退出):");int input;try {input = scanner.nextInt();} catch (Exception e) {return;}scanner.nextLine();switch (input) {case 1:addStudent(scanner);break;case 2:addBook(scanner);break;case 3:addBorrow(scanner);break;case 4:showBorrow();break;case 5:showStudent();break;case 6:showBook();break;default:return;}}} catch (IOException e) {e.printStackTrace();}}private static void showStudent() {SqlUtil.doSqlWork(mapper -> {mapper.getStudentList().forEach(student -> {System.out.println(student.getSid() + "." + student.getName() + " " + student.getSex() + " " + student.getGrade() + "级");});});}private static void showBook() {SqlUtil.doSqlWork(mapper -> {mapper.getBookList().forEach(book -> {System.out.println(book.getBid() + "." + book.getTitle() + "[" + book.getPrice() + "] (" + book.getDesc() + ")");});});}private static void showBorrow() {SqlUtil.doSqlWork(mapper -> {mapper.getBorrowList().forEach(borrow -> {System.out.println(borrow.getStudent().getName() + " -> " + borrow.getBook().getTitle());});});}private static void addBorrow(Scanner scanner) {System.out.print("请输入书籍号:");String a = scanner.nextLine();int bid = Integer.parseInt(a);System.out.print("请输入学号:");String b = scanner.nextLine();int sid = Integer.parseInt(b);SqlUtil.doSqlWork(mapper -> mapper.addBorrow(sid, bid));}private static void addBook(Scanner scanner) {System.out.print("请输入书籍标题:");String title = scanner.nextLine();System.out.print("请输入书籍介绍:");String desc = scanner.nextLine();System.out.print("请输入书籍价格:");String price = scanner.nextLine();Double p = Double.parseDouble(price);Book book = new Book(title, desc, p);SqlUtil.doSqlWork(mapper -> {int i = mapper.addBook(book);if (i > 0) {System.out.println("书籍信息录入成功!");log.info("新添加了一条书籍信息" + book);} else System.out.println("书籍信息录入失败,请重试!");});}private static void addStudent(Scanner scanner) {System.out.print("请输入学生名字:");String name = scanner.nextLine();System.out.print("请输入学生性别(男/女):");String sex = scanner.nextLine();System.out.print("请输入学生年级:");String grade = scanner.nextLine();int g = Integer.parseInt(grade);Student student = new Student(name, sex, g);SqlUtil.doSqlWork(mapper -> {int i = mapper.addStudent(student);if (i > 0) {System.out.println("学生信息录入成功!");log.info("新添加了一条学生信息" + student);} else System.out.println("学生信息录入失败,请重试!");});}
}

SqlUtil

public class SqlUtil {private SqlUtil(){}private static SqlSessionFactory factory;static {try {factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));} catch (IOException e) {throw new RuntimeException(e);}}//    public SqlSession getSession() {//        return factory.openSession(true);
//    }public static void doSqlWork(Consumer<BookMapper> consumer) {try (SqlSession sqlSession = factory.openSession(true)) {BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);consumer.accept(bookMapper);}}
}

BookMapper

public interface BookMapper {@Insert("insert into student(name, sex, grade) values(#{name}, #{sex}, #{grade})")int addStudent(Student student);@Insert("insert into book(title, `desc`, price) values(#{title}, #{desc}, #{price})")int addBook(Book book);@Insert("insert into borrow(sid, bid) values(#{sid}, #{bid})")int addBorrow(@Param("sid") int sid, @Param("bid") int bid);@Results({@Result(column = "id", property = "id", id = true),@Result(column = "sid", property = "student", one = @One(select = "getStudentBySid")),@Result(column = "bid", property = "book", one = @One(select = "getBookByBid")),})@Select("select * from borrow")List<Borrow> getBorrowList();@Select("select * from student")List<Student> getStudentList();@Select("select * from book")List<Book> getBookList();@Select("select * from student where sid = #{sid}")Student getStudentBySid(int sid);@Select("select * from book where bid = #{bid}")Book getBookByBid(int bid);
}

Book

@Data
@NoArgsConstructor
public class Book {int bid;String title;String desc;double price;public Book(String title, String desc, double price) {this.title = title;this.desc = desc;this.price = price;}
}

Borrow

@Data
public class Borrow {int id;Student student;Book book;
}

Student

@Data
@NoArgsConstructor
public class Student {int sid;String name;String sex;int grade;public Student(String name, String sex, int grade) {this.name = name;this.sex = sex;this.grade = grade;}
}

1、新建数据库+创建3张表

字段示例:


然后再点击上面的“+”号 添加字段

注意这个“name”只要输入名称,自动显示为varchar(长度255),然后再点击“不是null”

这里sex要改一下,改成枚举enum(且修改删除长度),然后在下面的“枚举值”中设置“男”“女”,“默认值”为“男”

grade要手动修改int且删除长度

注意所有字段都修改为 非null

student表

book表


(这个id也是自动递增的)

borrow表


(这个id也是自动递增的)

然后点击“字段”右边的“外键”来添加外键


然后点击“索引”添加索引(因为学生借书的这个信息只可能存在一个,同一个学生只可能借同一本书,索引类型就是“UNIQUE”,比如“sid = 1,bid = 1”只能存在一条,不能有第二条,所以建立一个索引)

触发器

然后点击“触发器”添加触发器(比如要删除一个学生的数据,要跟着把跟这个学生相关联的借书的信息都删除)

学生表中:
在删除这个学生之前就要把借阅的信息给删除,因此是“BEFORE”


book表:

2、创建项目+导入依赖+在线录入学生信息和书籍信息


pom.xml

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>BookManager</artifactId><version>1.0-SNAPSHOT</version><name>BookManager</name><!-- FIXME change it to the project's website --><url>http://www.example.com</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version><scope>provided</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.1</version><scope>test</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.7</version></dependency></dependencies></project>

然后在右边“数据库”中添加数据源

resources/mybatis-config.xml
(这里URL先从创建数据源然后从中得到)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/book_manage"/><property name="username" value="root"/><property name="password" value="root123456"/></dataSource></environment></environments>
</configuration>

book.manage.mapper.BookMapper接口
(sid不用填,因为刚才设置成了自动递增)

public interface BookMapper {@Insert("insert into student(name, sex, grade) values(#{name}, #{sex}, #{grade})")int addStudent(Student student);
}

然后在mybatis-config.xml中添加

<mappers><mapper class="book.manage.mapper.BookMapper"/>
</mappers>

book.manage.Main
(注意这里不要用new FileInputStream,因为这个不是外部文件,而是用Resources.getResourceAsReader)

public class Main {public static void main(String[] args) throws IOException {SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));try (SqlSession sqlSession = factory.openSession(true)) {BookMapper mapper = sqlSession.getMapper(BookMapper.class);System.out.println(mapper.addStudent(new Student("小明", "男", 2019)));}}
}

(测试成功后将student表中新出现的这条记录删除)

book.manage.entity.Student
(@Data中自带了@RequiredArgsConstructor,会把final自动设定为必须的属性!!)
(后面发现还是不要加final!!!会错误)

@Data
public class Student {int sid;final String name;final String sex;final int grade;
}

book.manage.entity.Book


@Data
public class Book {int bid;final String title;final String desc;final double price;
}

BookMapper接口中添加
(注意这个desc是关键字,必须加上``)

@Insert("insert into book(title, `desc`, price) values(#{title}, #{desc}, #{price})")
int addBook(Book book);

book.manage.sql.SqlUtil

public class SqlUtil {private SqlUtil(){}private static SqlSessionFactory factory;static {try {factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));} catch (IOException e) {throw new RuntimeException(e);}}public SqlSession getSession() {return factory.openSession(true);}
}

以上工具都就位了,现在我们来写在线录入学生信息和书籍信息

book.manage.Main

public class Main {public static void main(String[] args) throws IOException {try (Scanner scanner = new Scanner(System.in)) {while (true) {System.out.println("========================");System.out.println("1. 录入学生信息");System.out.println("2. 录入书籍信息");System.out.print("输入您想要执行的操作(输入其他任意数字退出):");int input;try {input = scanner.nextInt();} catch (Exception e) {return;}switch (input) {case 1:break;case 2:break;default:return;}}}}
}

注意 scanner.nextInt不会把换行符传进去,所以在下面catch外面用nextLine清理一下

将book.manage.mapper.BookMapper中原先的getSession换成doSqlWork
这个方法要求传入一个函数式,这个函数式会给你一个BookMapper进行消费

本来的话addStudent要把Student导入数据库我们要先获得sqlSession然后获取BookMapper通过mapper把数据弄到数据库中
但是我们这里简化一下,由于后面的操作都是一样的套路,因此我们这里直接给它封装一下

book.manage.Main

package book.manage;import book.manage.entity.Book;
import book.manage.entity.Student;
import book.manage.mapper.BookMapper;
import book.manage.sql.SqlUtil;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Scanner;public class Main {public static void main(String[] args) throws IOException {try (Scanner scanner = new Scanner(System.in)) {while (true) {System.out.println("========================");System.out.println("1. 录入学生信息");System.out.println("2. 录入书籍信息");System.out.print("输入您想要执行的操作(输入其他任意数字退出):");int input;try {input = scanner.nextInt();} catch (Exception e) {return;}scanner.nextLine();switch (input) {case 1:addStudent(scanner);break;case 2:addBook(scanner);break;default:return;}}}}private static void addBook(Scanner scanner) {System.out.print("请输入书籍标题:");String title = scanner.nextLine();System.out.print("请输入书籍介绍:");String desc = scanner.nextLine();System.out.print("请输入书籍价格:");String price = scanner.nextLine();Double p = Double.parseDouble(price);Book book = new Book(title, desc, p);SqlUtil.doSqlWork(mapper -> {int i = mapper.addBook(book);if (i > 0) System.out.println("书籍信息录入成功!");else System.out.println("书籍信息录入失败,请重试!");});}private static void addStudent(Scanner scanner) {System.out.print("请输入学生名字:");String name = scanner.nextLine();System.out.print("请输入学生性别(男/女):");String sex = scanner.nextLine();System.out.print("请输入学生年级:");String grade = scanner.nextLine();int g = Integer.parseInt(grade);Student student = new Student(name, sex, g);SqlUtil.doSqlWork(mapper -> {int i = mapper.addStudent(student);if (i > 0) System.out.println("学生信息录入成功!");else System.out.println("学生信息录入失败,请重试!");});}
}

book.manage.sql.SqlUtil

package book.manage.sql;import book.manage.mapper.BookMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;
import java.util.function.Consumer;public class SqlUtil {private SqlUtil(){}private static SqlSessionFactory factory;static {try {factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));} catch (IOException e) {throw new RuntimeException(e);}}//    public SqlSession getSession() {//        return factory.openSession(true);
//    }public static void doSqlWork(Consumer<BookMapper> consumer) {try (SqlSession sqlSession = factory.openSession(true)) {BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);consumer.accept(bookMapper);}}
}

3、完整的日志系统

录入学生信息和书籍信息是敏感操作,而打印日志一般是只有敏感操作才需要去打印日志;日志往文件里去打,就不要打在控制台了(因为控制台是在运行菜单)

resources/logging.properties

# RootLogger 的默认处理器为
handlers= java.util.logging.FileHandler
# RootLogger 的默认的日志级别
.level= ALLjava.util.logging.FileHandler.pattern=console.log
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter

然后我们就要去读配置了,这里直接用lombok
在 Main 类上加一个@Log
由于resources是内部的,我们用mybatis提供的Resources.getR…

package book.manage;import book.manage.entity.Book;
import book.manage.entity.Student;
import book.manage.mapper.BookMapper;
import book.manage.sql.SqlUtil;
import lombok.extern.java.Log;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Scanner;
import java.util.logging.LogManager;@Log
public class Main {public static void main(String[] args) throws IOException {try (Scanner scanner = new Scanner(System.in)) {LogManager manager = LogManager.getLogManager();manager.readConfiguration(Resources.getResourceAsStream("logging.properties"));while (true) {System.out.println("========================");System.out.println("1. 录入学生信息");System.out.println("2. 录入书籍信息");System.out.print("输入您想要执行的操作(输入其他任意数字退出):");int input;try {input = scanner.nextInt();} catch (Exception e) {return;}scanner.nextLine();switch (input) {case 1:addStudent(scanner);break;case 2:addBook(scanner);break;default:return;}}} catch (IOException e) {e.printStackTrace();}}private static void addBook(Scanner scanner) {System.out.print("请输入书籍标题:");String title = scanner.nextLine();System.out.print("请输入书籍介绍:");String desc = scanner.nextLine();System.out.print("请输入书籍价格:");String price = scanner.nextLine();Double p = Double.parseDouble(price);Book book = new Book(title, desc, p);SqlUtil.doSqlWork(mapper -> {int i = mapper.addBook(book);if (i > 0) {System.out.println("书籍信息录入成功!");log.info("新添加了一条书籍信息" + book);} else System.out.println("书籍信息录入失败,请重试!");});}private static void addStudent(Scanner scanner) {System.out.print("请输入学生名字:");String name = scanner.nextLine();System.out.print("请输入学生性别(男/女):");String sex = scanner.nextLine();System.out.print("请输入学生年级:");String grade = scanner.nextLine();int g = Integer.parseInt(grade);Student student = new Student(name, sex, g);SqlUtil.doSqlWork(mapper -> {int i = mapper.addStudent(student);if (i > 0) {System.out.println("学生信息录入成功!");log.info("新添加了一条学生信息" + student);} else System.out.println("学生信息录入失败,请重试!");});}
}

运行后,产生了一个根目录下的 console.log

发现把Mybatis的也打了,因此我们在mybatis-config.xml中加上setting,name为logImpl

<settings><setting name="logImpl" value="NO_LOGGING"/>
</settings>

就变成了

然后在logging.properties中加上,表示追加

java.util.logging.FileHandler.append=true

这样的话重新运行程序后新的日志会加上上一次程序运行的日志后面,不会覆盖

4、添加借阅信息

(注意由于这里有两个参数,@Param(“sid”)

@Insert("insert into borrow(sid, bid) values(#{sid}, #{bid})")
int addBorrow(@Param("sid") int sid, @Param("bid") int bid);
private static void addBorrow(Scanner scanner) {System.out.print("请输入书籍号:");String a = scanner.nextLine();int bid = Integer.parseInt(a);System.out.print("请输入学号:");String b = scanner.nextLine();int sid = Integer.parseInt(b);SqlUtil.doSqlWork(mapper -> mapper.addBorrow(sid, bid));
}

5、查询借阅信息列表

book.manage.entity.Borrow

@Data
public class Borrow {int id;Student student;Book book;
}

BookMapper接口
(id = true表示id是唯一的)

@Results({@Result(column = "id", property = "id", id = true),@Result(column = "sid", property = "student", one = @One(select = "getStudentBySid")),@Result(column = "bid", property = "book", one = @One(select = "getBookByBid")),
})
@Select("select * from borrow")
List<Borrow> getBorrowList();@Select("select * from student where sid = #{sid}")
Student getStudentBySid(int sid);@Select("select * from book where bid = #{bid}")
Book getBookByBid(int bid);

test/java/com.test.MainTest 编写测试用例

public class MainTest {@Testpublic void test1() {SqlUtil.doSqlWork(mapper -> {mapper.getBorrowList().forEach(System.out::println);});}
}

以上这样会报错的

Student实体类中,刚才我们将三个字段设定成final,三个参数无法确定是什么类型,因此我们将final都去掉,再单独写一个三个参数的构造方法!
还要添加一个无参构造

@Data
@NoArgsConstructor
public class Student {int sid;String name;String sex;int grade;public Student(String name, String sex, int grade) {this.name = name;this.sex = sex;this.grade = grade;}
}

同理,Book类也要变成:

@Data
@NoArgsConstructor
public class Book {int bid;String title;String desc;double price;public Book(String title, String desc, double price) {this.title = title;this.desc = desc;this.price = price;}
}

然后得到

Borrow(id=1, student=Student(sid=4, name=小张, sex=男, grade=2222), book=Book(bid=3, title=标题1, desc=介绍1, price=1.11))

book.manage.Main

private static void showBorrow() {SqlUtil.doSqlWork(mapper -> {mapper.getBorrowList().forEach(borrow -> {System.out.println(borrow.getStudent().getName() + " -> " + borrow.getBook().getTitle());});});
}

6、查询学生信息列表+查询书籍信息列表

@Select("select * from student")
List<Student> getStudentList();@Select("select * from book")
List<Book> getBookList();
@Test
public void test2() {SqlUtil.doSqlWork(mapper -> {mapper.getStudentList().forEach(System.out::println);});
}@Test
public void test3() {SqlUtil.doSqlWork(mapper -> {mapper.getBookList().forEach(System.out::println);});
}


测试过了,接下来在Main中写

private static void showStudent() {SqlUtil.doSqlWork(mapper -> {mapper.getStudentList().forEach(student -> {System.out.println(student.getSid() + "." + student.getName() + " " + student.getSex() + " " + student.getGrade() + "级");});});
}private static void showBook() {SqlUtil.doSqlWork(mapper -> {mapper.getBookList().forEach(book -> {System.out.println(book.getBid() + "." + book.getTitle() + "[" + book.getPrice() + "] (" + book.getDesc() + ")");});});
}

7、打包成jar文件

首先,要配置打包插件
(注意要修改里面archive的mainClass!!!)

<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>${slf4j.version}</version>
</dependency>
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>${slf4j.version}</version>
</dependency>

(?)

<dependency><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.0.0</version>
</dependency>
<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><!-- JUnit 5 requires Surefire version 2.22.0 or higher --><version>2.22.0</version></plugin><plugin><artifactId>maven-assembly-plugin</artifactId><version>3.0.0</version><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive><manifest><addClasspath>true</addClasspath><mainClass>book.manage.Main</mainClass></manifest></archive></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions></plugin></plugins><pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --><plugins><!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle --><plugin><artifactId>maven-clean-plugin</artifactId><version>3.1.0</version></plugin><!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging --><plugin><artifactId>maven-resources-plugin</artifactId><version>3.0.2</version></plugin><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.8.0</version></plugin><plugin><artifactId>maven-surefire-plugin</artifactId><version>2.22.1</version></plugin><plugin><artifactId>maven-jar-plugin</artifactId><version>3.0.2</version></plugin><plugin><artifactId>maven-install-plugin</artifactId><version>2.5.2</version></plugin><plugin><artifactId>maven-deploy-plugin</artifactId><version>2.8.2</version></plugin><!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle --><plugin><artifactId>maven-site-plugin</artifactId><version>3.7.1</version></plugin><plugin><artifactId>maven-project-info-reports-plugin</artifactId><version>3.0.0</version></plugin></plugins></pluginManagement></build>

然后点击maven的package

然后右键target中生成的jar-with-dependencies的jar包,点击 打开于Finder
然后拖到终端

即可运行jar包

且录入学生信息后,在根目录下还产生了一个console.log

JAVAWEB(三)Java与数据库(JUnit、JUL、Maven、图书管理系统)相关推荐

  1. Java+JSP+Mysql+Tomcat实现Web图书管理系统

    图书管理系统 一.系统介绍 1.软件环境 2.功能模块图 3.系统功能 4.数据库表 5.工程截图 二.系统展示 1.系统注册 2.系统登录 3.找回密码 4.管理员-主页 5.管理员-用户管理主页 ...

  2. java swing实训项目(图书管理系统)

    1.项目布局(供新手参考) 学校老师任务,因为我也是新手所以写的不是特别的好,所以可以提供参考. package GUI_Object.GUI;import GUI_Object.mysql.Data ...

  3. Java实操演练1——简单图书管理系统的设计与实现

    笔者将记录每一步的具体操作,展示自己在实际开发过程中遇到的各种问题并提供解决思路. 目录 一.开发环境 二.开发过程 1.使用idea创建一个new project 2.项目具体设计 3.按照项目设计 ...

  4. python连接MySQL数据库实现界面化图书管理系统

    文章目录 前言 一.程序架构 二.登录注册部分界面 三.学生界面 四.教师界面 五.管理员界面 六.总结 前言 提示:之前写了一个不连接数据库的图书管理系统,使用csv文件存储信息(链接:python ...

  5. java初级内容结课项目——图书管理系统

    在前面的十三课中,笔者已经介绍了有关java的各种语法及其细节.若要融会贯通,则还需大量的练习,而非单纯的依赖于笔记中的内容.毕竟"纸上得来终觉浅,绝知此事要躬行".在java语法 ...

  6. JAVA面向对象小项目之迷你图书管理系统

    系统概述 为某图书馆开发一个迷你图书管理系统,实现图书的管理,包括如下功能: 1.新增图书 2.查看图书 删除图书 4.借出图书 5.归还图书 6.退出图书系统 整体开发思路 系统开发步骤 明确需求 ...

  7. 【JAVA程序设计】基于SSM的图书管理系统-有论文文档

    基于SSM的图书管理系统-有文档 项目获取 项目简介 开发环境 项目技术 功能结构 文档目录 运行截图 项目获取 获取方式(点击下载):是云猿实战 项目经过多人测试运行,可以确保100%成功运行. 项 ...

  8. 基于java和sqlserver建立的简易图书管理系统

    简易图书管理系统 1.所需功能: 2.流程: 数据库连接 3.环境及其语言: 4.数据库建立 5.操作: 数据库连接:sql.T3.Test1.java 选择需要的操作:sql.T3.Test1.ja ...

  9. Java学习记录之简单的图书管理系统(完善中)

    电子书管理系统 功能划分 最核心的功能 提供图书管理的服务 添加图书 查询 检索所有的图书 按已知检索图书信息 图书的删除 图书信息的更新 用户 用户的类型 管理员:图书的添加.更新.删除操作 普通用 ...

  10. 数据库课程设计之图书管理系统

    1 课程设计项目介绍 高校拥有一个小型图书馆,为全校师生提供一个阅读.学习的空间.近年来,随着生源不断扩大,图书馆的规模也随之扩大,图书数量也相应地大量增加,有关图书的各种信息成倍增加.面对如此庞大的 ...

最新文章

  1. BZOJ4105 THUSC2015平方运算(线段树)
  2. 一键生成表结构说明文档的参考,数据字典生成方式参考
  3. PhpCms V9调用指定栏目子栏目文章的两种方法
  4. 09_Redis持久化——AOF方式
  5. 【Windows 10 应用开发】使用快捷访问键
  6. Boost:容器std::pair与宏BOOST_TEST_EQ相关的测试
  7. 郴州郴锦机器人_减税降费宣传走进郴州市民营企业高质量发展专题培训班
  8. 【Qt】Qt之进程间通信(Windows消息)【转】
  9. LeetCode 809. 情感丰富的文字
  10. python语言的核心理念是_Python 编程语言的核心是什么?
  11. 数据库一键自动生成 Java 实体类和数据库文档
  12. sscanf %*s
  13. MD5摘要算法的几种破解方法!
  14. 含有一个量词的命题的否命题_高一 | 数学必修一全称量词与存在量词知识点总结...
  15. ERNIE: Enhanced Representation through Knowledge Integration, Yu Sun 2020【representation+Bert+ner】
  16. windows自定义屏幕大小,分辨率大小,自定义电脑屏幕分辨率
  17. Matplotlib双y轴+调整图例位置
  18. jscese 知其白 守其黑 為天下式 __Read The Fucking Source Code的博客汇总
  19. 第一章-走近群智感知,辨识庐山真面目
  20. 知识图谱:如何推动金融更智能?

热门文章

  1. 栈和队列的相同点和不同点
  2. vue+weui 手机端项目
  3. java集合之trove4j高性能集合库
  4. 2021年全球AFM探针收入大约230.8百万美元,预计2028年达到374.9百万美元
  5. 掌控堆栈确保系统稳定 IAR技术手册翻译
  6. 分布式环境下对部分热数据(如redis热key,热请求)进行探测,并对探测结果及时同步到各个client实例的JVM内存的方案简述
  7. 蓝桥幼儿园(蓝桥杯)
  8. JavaScript:鼠标拖曳效果
  9. 深度学习和大数据之间,主要是什么关系?
  10. Android 直播RTMP流