暂时未有相关云产品技术能力~
在本文中会继续详细的为大家讲解如何在 SpringBoot 中更简单的发送邮件。导入 Mail 和 Thymeleaf 依赖<!--mail依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <!--thymeleaf依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>在 application.yml 文件中设置参数邮箱授权码获取教程:Java 使用 JavaMailSenderImpl 实现发送 QQ 邮件#设置邮箱 spring: mail: host: smtp.qq.com #邮箱服务器地址 username: test@qq.com #邮箱账号 password: ********** #邮箱授权码 default-encoding: utf-8 #默认编码 #邮件发件人 mail: fromMail: fromAddress: test@qq.com 编写验证码的生成类/** * @author Jie */ @Service public class VerificationCodeServiceImpl implements VerificationCodeService { /** * 生产的验证码位数 */ private final int generateVerificationCodeLength=4; private final String[] metaCode={"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}; @Override public String generateVerificationCode() { Random random = new Random(); StringBuilder verificationCode = new StringBuilder(); while (verificationCode.length()<generateVerificationCodeLength){ int i = random.nextInt(metaCode.length); verificationCode.append(metaCode[i]); } return verificationCode.toString(); } }编写验证码 HTML 模板<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>邮箱验证码</title> <style> table { width: 700px; margin: 0 auto; } #top { width: 700px; border-bottom: 1px solid #ccc; margin: 0 auto 30px; } #top table { font: 12px Tahoma, Arial, 宋体; height: 40px; } #content { width: 680px; padding: 0 10px; margin: 0 auto; } #content_top { line-height: 1.5; font-size: 14px; margin-bottom: 25px; color: #4d4d4d; } #content_top strong { display: block; margin-bottom: 15px; } #content_top strong span { color: #f60; font-size: 16px; } #verificationCode { color: #f60; font-size: 24px; } #content_bottom { margin-bottom: 30px; } #content_bottom small { display: block; margin-bottom: 20px; font-size: 12px; color: #747474; } #bottom { width: 700px; margin: 0 auto; } #bottom div { padding: 10px 10px 0; border-top: 1px solid #ccc; color: #747474; margin-bottom: 20px; line-height: 1.3em; font-size: 12px; } #content_top strong span { font-size: 18px; color: #FE4F70; } #sign { text-align: right; font-size: 18px; color: #FE4F70; font-weight: bold; } #verificationCode { height: 100px; width: 680px; text-align: center; margin: 30px 0; } #verificationCode div { height: 100px; width: 680px; } .button { color: #FE4F70; margin-left: 10px; height: 80px; width: 80px; resize: none; font-size: 42px; border: none; outline: none; padding: 10px 15px; background: #ededed; text-align: center; border-radius: 17px; box-shadow: 6px 6px 12px #cccccc, -6px -6px 12px #ffffff; } .button:hover { box-shadow: inset 6px 6px 4px #d1d1d1, inset -6px -6px 4px #ffffff; } </style> </head> <body> <table> <tbody> <tr> <td> <div id="top"> <table> <tbody><tr><td></td></tr></tbody> </table> </div> <div id="content"> <div id="content_top"> <strong>尊敬的用户:您好!</strong> <strong> 您正在进行<span>注册账号</span>操作,请在验证码中输入以下验证码完成操作: </strong> <div id="verificationCode"> <button class="button" th:each="a:${verifyCode}">[[${a}]]</button> </div> </div> <div id="content_bottom"> <small> 注意:此操作可能会修改您的密码、登录邮箱或绑定手机。如非本人操作,请及时登录并修改密码以保证帐户安全 <br>(工作人员不会向你索取此验证码,请勿泄漏!) </small> </div> </div> <div id="bottom"> <div> <p>此为系统邮件,请勿回复<br> 请保管好您的邮箱,避免账号被他人盗用 </p> <p id="sign">——Romantik</p> </div> </div> </td> </tr> </tbody> </table> </body>测试@SpringBootTest class ServiceApplicationTests { @Autowired private EmailService emailService; @Test void contextLoads() { boolean test = emailService.sendEmailVerificationCode("test.com"); System.out.println(test); } }查看邮件
传智书城的源码早已经泛滥了,但是网上下的有些有很多的坑,我就花了点时间完善和弥补了下里面的坑,添加了后台用户管理功能。源码下载地址:传智书城源码已修复.zip-Java文档类资源-CSDN文库源码使用说明数据库导入执行 itcaststore.sql 文件使用 Ecpilise 导入项目,配置 Tomcat 服务器在 c3p0-config.xml 中修改数据库 url,用户名,密码<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://127.0.0.1/itcaststore?userUnicode=true&amp;characterEncoding=utf8</property> <property name="user">root</property> <property name="password">root</property> </default-config> </c3p0-config> 发送 Email 邮件服务,需要在 cn.itcast.itcaststore.utils 包下,修改 MailUtilsAuthenticator auth = new Authenticator() { public PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("邮箱账号", "授权码"); } }; Session session = Session.getInstance(props, auth); // 2.创建一个Message,它相当于是邮件内容 Message message = new MimeMessage(session); message.setFrom(new InternetAddress("发送者")); // 设置发送者 ... }图书有些图片错误,可以按照网页的 F12 网络访问,按路径修改QQ 邮箱授权码获得教程:Java 使用 JavaMailSenderImpl 实现发送 QQ 邮件_ZOOM_Jie -CSDN博客管理员退出操作有点迷,自测 360 极速浏览器可以正常退出系统页面展示前台页面首页全部图书列表书籍信息页面登录页面用户注册页面用户页面用户账户信息界面用户信息修改页面用户订单查询页面用户订单详细信息页面订单支付页面订单页面购物车页面下订单页面订单生成系统后台后台首页商品管理页面添加页面修改页面销售榜单页面可以下载包含指定年月的销售情况的数据表订单管理页面订单信息查看页面公告管理页面公告修改页面公告添加页面用户管理页面修改用户信息界面
notLikeTable(LikeTable)模糊表排除(sql 过滤)likeTable 与 notLikeTable 只能配置一项addInclude(String…)增加表匹配(内存过滤)include 与 exclude 只能配置一项MyBatis-Plus 官方文档:https://mp.baomidou.com/guide/generator-new.html这是官网上的文档,从官方文档中给快速生成代码中,可以看出代码生成器的配置结构为: //1、配置数据源 FastAutoGenerator.create("url", "username", "password") //2、全局配置 .globalConfig(...) //3、包配置 .packageConfig(...) //4、策略配置 .strategyConfig(...) //5、模板引擎配置 .templateEngine(...) //6、执行 .execute();我们只需要通过填空的方式去配置数据源(DataSource),全局配置(GlobalConfig),包配置(PackageConfig),策略配置(StrategyConfig)和模板引擎配置(TemplateEngine)即可。官方文档上也给出了对于每一个配置我们可以进行什么操作。配置数据源配置(DataSource)属性说明示例urljdbc路径jdbc:mysql://127.0.0.1:3306/mybatis-plususername数据库账号rootpassword数据库密码123456dbQuery(IDbQuery)数据库查询new MySqlQuery()schema(String)数据库schema(部分数据库适用)mybatis-plustypeConvert(ITypeConvert)数据库类型转换器new MySqlTypeConvert()keyWordsHandler(IKeyWordsHandler)数据库关键字处理器new MySqlKeyWordsHandler()全局配置(GlobalConfig)方法说明示例fileOverride覆盖已生成文件默认值:falsedisableOpenDir禁止打开输出目录默认值:trueoutputDir(String)指定输出目录/opt/baomidou/ 默认值: windows:D:// linux or mac : /tmpauthor(String)作者名baomidou 默认值:作者enableKotlin开启 kotlin 模式默认值:falseenableSwagger开启 swagger 模式默认值:falsedateType(DateType)时间策略DateType.ONLY_DATE=Date 默认值: DateType.TIME_PACK=LocalDateTimecommentDate(String)注释日期默认值: yyyy-MM-dd包配置(PackageConfig)方法说明示例parent(String)父包名默认值:com.baomidoumoduleName(String)父包模块名默认值:无entity(String)Entity 包名默认值:entityservice(String)Service 包名默认值:serviceserviceImpl(String)Service Impl 包名默认值:service.implmapper(String)Mapper 包名默认值:mappermapperXml(String)Mapper XML 包名默认值:mapper.xmlcontroller(String)Controller 包名默认值:controllerother(String)自定义文件包名输出自定义文件时所用到的包名pathInfo(Map<OutputFile, String>)路径配置信息Collections.singletonMap(OutputFile.mapperXml, “D://”)策略配置(StrategyConfig)方法说明示例enableCapitalMode开启大写命名默认值:falseenableSkipView开启跳过视图默认值:falsedisableSqlFilter禁用 sql 过滤默认值:true,语法不能支持使用 sql 过滤表的话,可以考虑关闭此开关enableSchema启用 schema默认值:false,多 schema 场景的时候打开likeTable(LikeTable)模糊表匹配(sql 过滤)likeTable 与 notLikeTable 只能配置一项notLikeTable(LikeTable)模糊表排除(sql 过滤)likeTable 与 notLikeTable 只能配置一项addInclude(String…)增加表匹配(内存过滤)include 与 exclude 只能配置一项addExclude(String…)增加表排除匹配(内存过滤)include 与 exclude 只能配置一项addTablePrefix(String…)增加过滤表前缀addTableSuffix(String…)增加过滤表后缀addFieldPrefix(String…)增加过滤字段前缀addFieldSuffix(String…)增加过滤字段后缀entityBuilder实体策略配置controllerBuildercontroller 策略配置mapperBuildermapper 策略配置serviceBuilderservice 策略配置模板引擎配置(TemplateEngine)默认 Velocity ;可选模板引擎 Beetl 或 Freemarker。模板引擎代码Velocity默认.templateEngine(new VelocityTemplateEngine())Freemarker可选.templateEngine(new FreemarkerTemplateEngine())Beetl可选.templateEngine(new BeetlTemplateEngine())代码生成器测试样例那么知道配置之后我们可以自己写一个操作一下。步骤:1、创建测试数据库 mpCREATE DATABASE mp; USE `mp`; /*Table structure for table `student` */ CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '学号', `name` varchar(50) DEFAULT NULL COMMENT '姓名', `score` double DEFAULT NULL COMMENT '成绩', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*Table structure for table `user` */ CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id', `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(50) DEFAULT NULL COMMENT '密码', `create_time` date DEFAULT NULL COMMENT '创建时间', `modify_time` date DEFAULT NULL COMMENT '最后一次修改时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;FieldTypeCommentidint用户idusernamevarchar(50)用户名passwordvarchar(50)密码create_timedate创建时间modify_timedate最后一次修改时间FieldTypeCommentidint学号namevarchar(50)名字scoredouble成绩2、创建一个 Spring-Boot 项目3、在 pom.xml 中导入相关依赖<!--spring-boot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--SpringBootTest--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--swagger--> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>1.6.3</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--mysql 驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3.4</version> </dependency> <!--mybatis-plus-generator 生成器--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.1</version> </dependency> <!--velocity--> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version> </dependency> <!--freemarker--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.31</version> </dependency> <!--beetl 模板--> <dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl</artifactId> <version>3.8.1.RELEASE</version> </dependency>注:模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker,实际使用中只导入使用模板对应的依赖即可,不用全部导入。4、编写一个mian方法,加上框架public static void main(String[] args) { //1、配置数据源 FastAutoGenerator.create("url", "username", "password") //2、全局配置 .globalConfig(...) //3、包配置 .packageConfig(...) //4、策略配置 .strategyConfig(...) //5、模板引擎配置 .templateEngine(...) //6、执行 .execute(); } 5、进行数据源配置public static void main(String[] args) { //1、配置数据源 FastAutoGenerator.create("jdbc:mysql://localhost:3306/mp", "root", "123456") //2、全局配置 .globalConfig(...) //3、包配置 .packageConfig(...) //4、策略配置 .strategyConfig(...) //5、模板引擎配置 .templateEngine(...) //6、执行 .execute(); }6、进行全局配置注:3.5.1+版本开始支持 lambda 表达式 //2、全局配置 .globalConfig(builder -> { builder.author("Jie") // 设置作者名 .outputDir(System.getProperty("user.dir") + "/src/main/java") //设置输出路径 .commentDate("yyyy-MM-dd hh:mm:ss") //注释日期 .dateType(DateType.ONLY_DATE) //定义生成的实体类中日期的类型 TIME_PACK=LocalDateTime;ONLY_DATE=Date; .fileOverride() //覆盖之前的文件 .enableSwagger() //开启 swagger 模式 .disableOpenDir(); //禁止打开输出目录,默认打开 }); 7、进行 包配置 //3、包配置 .packageConfig(builder -> { builder.parent("com") // 设置父包名 .moduleName("mp") //设置模块包名 .entity("entity") //pojo 实体类包名 .service("service") //Service 包名 .serviceImpl("serviceImpl") // ***ServiceImpl 包名 .mapper("mapper") //Mapper 包名 .xml("mapper") //Mapper XML 包名 .controller("controller") //Controller 包名 .other("utils") //自定义文件包名 .pathInfo(Collections.singletonMap(OutputFile.mapperXml, System.getProperty("user.dir")+"/src/main/resources/mapper")) //配置 **Mapper.xml 路径信息:项目的 resources 目录的 Mapper 目录下 }); 8、策略配置策略配置中需要对 Mapper,Service,Entity,Controller 类的生成策略进行单独的配置。 //4、策略配置 .strategyConfig(builder -> { builder.addInclude("user", "student") // 设置需要生成的数据表名 .addTablePrefix("t_", "c_") // 设置过滤表前缀 //4.1、Mapper策略配置 .mapperBuilder() .superClass(BaseMapper.class) //设置父类 .formatMapperFileName("%sMapper") //格式化 mapper 文件名称 .enableMapperAnnotation() //开启 @Mapper 注解 .formatXmlFileName("%sXml"); //格式化 Xml 文件名称 //4.2、service 策略配置 .serviceBuilder() .formatServiceFileName("%sService") //格式化 service 接口文件名称,%s进行匹配表名,如 UserService .formatServiceImplFileName("%sServiceImpl") //格式化 service 实现类文件名称,%s进行匹配表名,如 UserServiceImpl //4.3、实体类策略配置 .entityBuilder() .enableLombok() //开启 Lombok .disableSerialVersionUID() //不实现 Serializable 接口,不生产 SerialVersionUID .logicDeleteColumnName("deleted") //逻辑删除字段名 .naming(NamingStrategy.underline_to_camel) //数据库表映射到实体的命名策略:下划线转驼峰命 .columnNaming(NamingStrategy.underline_to_camel) //数据库表字段映射到实体的命名策略:下划线转驼峰命 .addTableFills( new Column("create_time", FieldFill.INSERT), new Column("modify_time", FieldFill.INSERT_UPDATE) ) //添加表字段填充,"create_time"字段自动填充为插入时间,"modify_time"字段自动填充为插入修改时间 .enableTableFieldAnnotation() // 开启生成实体时生成字段注解 //4.4、Controller策略配置 .controllerBuilder() .formatFileName("%sController") //格式化 Controller 类文件名称,%s进行匹配表名,如 UserController .enableRestStyle() //开启生成 @RestController 控制器 }) 9、模板引擎配置 //5、模板引擎 .templateEngine(new VelocityTemplateEngine()) //默认 /* .templateEngine(new FreemarkerTemplateEngine()) .templateEngine(new BeetlTemplateEngine()) */10、执行 //6、执行 .execute();执行效果演示:注:这里使用的是附2的交互式生成,两者源码几乎没有区别。附1:快速生成样例代码 public class Generator { public static void main(String[] args) { //1、配置数据源 FastAutoGenerator.create("jdbc:mysql://localhost:3306/yeb", "root", "123456") //2、全局配置 .globalConfig(builder -> { builder.author("Jie") // 设置作者名 .outputDir(System.getProperty("user.dir") + "/src/main/java") //设置输出路径:项目的 java 目录下 .commentDate("yyyy-MM-dd hh:mm:ss") //注释日期 .dateType(DateType.ONLY_DATE) //定义生成的实体类中日期的类型 TIME_PACK=LocalDateTime;ONLY_DATE=Date; .fileOverride() //覆盖之前的文件 .enableSwagger() //开启 swagger 模式 .disableOpenDir(); //禁止打开输出目录,默认打开 }) //3、包配置 .packageConfig(builder -> { builder.parent("com") // 设置父包名 .moduleName("mp") //设置模块包名 .entity("pojo") //pojo 实体类包名 .service("service") //Service 包名 .serviceImpl("serviceImpl") // ***ServiceImpl 包名 .mapper("mapper") //Mapper 包名 .xml("mapper") //Mapper XML 包名 .controller("controller") //Controller 包名 .other("utils") //自定义文件包名 .pathInfo(Collections.singletonMap(OutputFile.mapperXml, System.getProperty("user.dir") + "/src/main/resources/mapper")); //配置 mapper.xml 路径信息:项目的 resources 目录下 }) //4、策略配置 .strategyConfig(builder -> { builder.addInclude("user", "student") // 设置需要生成的数据表名 .addTablePrefix("t_", "c_") // 设置过滤表前缀 //4.1、Mapper策略配置 .mapperBuilder() .superClass(BaseMapper.class) //设置父类 .formatMapperFileName("%sMapper") //格式化 mapper 文件名称 .enableMapperAnnotation() //开启 @Mapper 注解 .formatXmlFileName("%sXml")//格式化 Xml 文件名称 //4.2、service 策略配置 .serviceBuilder() .formatServiceFileName("%sService") //格式化 service 接口文件名称,%s进行匹配表名,如 UserService .formatServiceImplFileName("%sServiceImpl") //格式化 service 实现类文件名称,%s进行匹配表名,如 UserServiceImpl //4.3、实体类策略配置 .entityBuilder() .enableLombok() //开启 Lombok .disableSerialVersionUID() //不实现 Serializable 接口,不生产 SerialVersionUID .logicDeleteColumnName("deleted") //逻辑删除字段名 .naming(NamingStrategy.underline_to_camel) //数据库表映射到实体的命名策略:下划线转驼峰命 .columnNaming(NamingStrategy.underline_to_camel) //数据库表字段映射到实体的命名策略:下划线转驼峰命 .addTableFills( new Column("create_time", FieldFill.INSERT), new Column("modify_time", FieldFill.INSERT_UPDATE) ) //添加表字段填充,"create_time"字段自动填充为插入时间,"modify_time"字段自动填充为插入修改时间 .enableTableFieldAnnotation() // 开启生成实体时生成字段注解 //4.4、Controller策略配置 .controllerBuilder() .formatFileName("%sController") //格式化 Controller 类文件名称,%s进行匹配表名,如 UserController .enableRestStyle(); //开启生成 @RestController 控制器 }) //5、模板 .templateEngine(new VelocityTemplateEngine()) /* .templateEngine(new FreemarkerTemplateEngine()) .templateEngine(new BeetlTemplateEngine()) */ //6、执行 .execute(); } }附2:交互式生成样例代码public static void main(String[] args) { Scanner scan = new Scanner(System.in); System.out.println("=====================数据库配置======================="); System.out.println("请输入 URL"); String url = scan.next(); System.out.println("请输入 username"); String username = scan.next(); System.out.println("请输入 password"); String password = scan.next(); FastAutoGenerator.create(url, username, password) // 全局配置 .globalConfig((scanner, builder) -> builder.author(scanner.apply("=====================全局配置=======================\n请输入作者名称?")) .outputDir(System.getProperty("user.dir") + "/src/main/java") .commentDate("yyyy-MM-dd hh:mm:ss") .dateType(DateType.TIME_PACK) .enableSwagger() .fileOverride() .enableSwagger() .disableOpenDir() ) // 包配置 .packageConfig((scanner, builder) -> builder.parent(scanner.apply("=====================包配置=======================\n请输入包名?")) .moduleName(scanner.apply("请输入父包模块名?")) .entity("entity") .service("service") .serviceImpl("serviceImpl") .mapper("mapper") .xml("mapper") .other("utils") .pathInfo(Collections.singletonMap(OutputFile.mapperXml, System.getProperty("user.dir")+"/src/main/resources/mapper")) ) // 策略配置 .strategyConfig((scanner, builder) -> { builder.addInclude(getTables(scanner.apply("=====================策略配置=======================\n请输入表名,多个英文逗号分隔?所有输入 all"))) .serviceBuilder() .formatServiceFileName("%sService") .formatServiceImplFileName("%sServiceImpl") .entityBuilder() //实体类策略配置 .enableLombok() //开启 Lombok .disableSerialVersionUID() .logicDeleteColumnName("deleted") //逻辑删除字段 .naming(NamingStrategy.underline_to_camel) .columnNaming(NamingStrategy.underline_to_camel) .addTableFills(new Column("create_time", FieldFill.INSERT), new Column("modify_time", FieldFill.INSERT_UPDATE)) .enableTableFieldAnnotation() // 开启生成实体时生成字段注解 .controllerBuilder() .formatFileName("%sController") .enableRestStyle() .mapperBuilder() .superClass(BaseMapper.class) .formatMapperFileName("%sMapper") .enableMapperAnnotation() //@mapper .formatXmlFileName("%sMapper"); }) /* 模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker .templateEngine(new BeetlTemplateEngine()) .templateEngine(new FreemarkerTemplateEngine()) */ .execute(); } // 处理 all 情况 protected static List<String> getTables(String tables) { return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(",")); }
安装【Chinese(Simplified)Language Pack/中文语言包】插件时报【Plugin Installation】错误解决方法:在官网上下载汉化语言包,离线安装点击链接:https://plugins.jetbrains.com/plugin/13710-chinese-simplified-language-pack/选择 IDE 对应的版本进行下载运行 IDE ,选择 【Install Plugin from Disk…】,选择下载的汉化语言包点击【Restart IDE】等待重启,汉化完成
IDEA 报错:org.apache.jasper.JasperException: 无法在web.xml或使用此应用程序部署的jar文件中解析绝对uri:[http://java.sun.com/jsp/jstl/core]IDEA上,运行JavaWeb工程报错:org.apache.jasper.JasperException: 无法在web.xml或使用此应用程序部署的jar文件中解析绝对uri:[http://java.sun.com/jsp/jstl/core]16-May-2021 20:09:31.289 信息 [http-nio-8080-exec-7] com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl.info {dataSource-1} inited java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at web.BaseServlet.doPost(BaseServlet.java:34) at web.BaseServlet.doGet(BaseServlet.java:19) at javax.servlet.http.HttpServlet.service(HttpServlet.java:626) at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:687) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748) Caused by: org.apache.jasper.JasperException: 无法在web.xml或使用此应用程序部署的jar文件中解析绝对uri:[http://java.sun.com/jsp/jstl/core] at org.apache.jasper.compiler.DefaultErrorHandler.jspError(DefaultErrorHandler.java:55) at org.apache.jasper.compiler.ErrorDispatcher.dispatch(ErrorDispatcher.java:294) at org.apache.jasper.compiler.ErrorDispatcher.jspError(ErrorDispatcher.java:81) at org.apache.jasper.compiler.TagLibraryInfoImpl.generateTldResourcePath(TagLibraryInfoImpl.java:251) at org.apache.jasper.compiler.TagLibraryInfoImpl.<init>(TagLibraryInfoImpl.java:122) at org.apache.jasper.compiler.Parser.parseTaglibDirective(Parser.java:431) at org.apache.jasper.compiler.Parser.parseDirective(Parser.java:489) at org.apache.jasper.compiler.Parser.parseElements(Parser.java:1445) at org.apache.jasper.compiler.Parser.parse(Parser.java:144) at org.apache.jasper.compiler.ParserController.doParse(ParserController.java:244) at org.apache.jasper.compiler.ParserController.parse(ParserController.java:105) at org.apache.jasper.compiler.Compiler.generateJava(Compiler.java:207) at org.apache.jasper.compiler.Compiler.compile(Compiler.java:392) at org.apache.jasper.compiler.Compiler.compile(Compiler.java:368) at org.apache.jasper.compiler.Compiler.compile(Compiler.java:352) at org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:605) at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:400) at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:378) at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:326) at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:710) at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:457) at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:384) at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:312) at web.BookServlet.list(BookServlet.java:48) ... 30 more在IDEA上,JSTL标签库的jar包已经导入但是成功部署之后,跳转页面会一直加载,不显示内容解决办法官网下载地址:http://archive.apache.org/dist/jakarta/taglibs/standard/binaries/将【taglibs-standard-impl.jar】下META-INF目录下需要引入的 tld 文件复制到 WEB-INF 目录下。将需要的tld 文件复制到 WEB-INF 目录下之后重新部署即可解决
第1关:指定类型JDBC封装任务描述本关任务:按照要求封装一个JDBC工具类。相关知识为了完成本关任务,你需要掌握JDBC的基本使用,可参考上一个实训内容 Java高级特性 - JDBC(上)。本章节将针对已知数据结构的某张表进行JDBC的封装。连接数据库在增删改查的过程中,我们都离不开数据库的连接,因此我们可以将其操作封装成一个方法,方法无需参数,将连接对象作为返回值。在方法中完成驱动加载和数据库连接即可使用:private static Connection getConnection() { //1.加载驱动 //2.连接数据库 Connection conn=null; //返回连接对象 return conn; }封装完后我们就可直接在增删改查中直接使用该方法了。关闭数据库连接同样每次连接完数据库我们都需要对相应资源进行释放,我们也将其封装为一个方法,方法参数为经常被使用到的对象,这些对象通常是ResultSet, Statement和Connection,因此我们的关闭连接方法如下:public static void close(ResultSet rs,PreparedStatement ps,Connection conn){ try { if(rs!=null) rs.close(); if(ps!=null) ps.close(); if(conn!=null) conn.close(); } catch (SQLException e) { e.printStackT\frace(); } }新增数据现我们数据库中已有的一张新闻表news,结构如下:字段名称类型备注约束idint新闻编号主键titlevarchar(60)新闻标题无author_namevarchar(30)作者名称无根据表结构我们创建一个News对象:public class News { private int id; private String title; private String anthor_name; public News(int id, String title, String anthor_name) { super(); this.id = id; this.title = title; this.anthor_name = anthor_name; } public int getId() { return id; } public void setId(int id) { this.id = id; } ......//省略其他属性的get、set方法 }日常生活中我们每天都会看到很多新闻,因此我们需要往数据库中不断新增最新新闻,下面我们一起来对新增方法进行封装:封装前我们先对方法进行构思,该方法需传递一个News对象,无需返回值,因此方法的定义和实现思路如下:public void insert(News news) throws SQLException { Connection conn = getConnection();//拿到连接对象 PreparedStatement ps = null; //编写新增sql语句 String sql = ""; try{ ps = conn.prepareStatement(sql); //通过传入的news对象对预编译中 ? 进行赋值 ps.setXXX(1,news.getXXX()); //执行新增sql语句 ps.executeUpdate(); }catch(SQLException e){ e.printStackT\frace(); }finally{ //关闭连接 close(null, ps, conn); } }编程要求在右侧编辑器补充代码,完成数据库连接、删除、更新以及查找方法。其中删除方法是通过用户传入新闻id来进行删除。注意:连接数据库名为mysql_db,数据库用户为root,密码为123123。测试说明平台会对你编写的代码进行测试:测试输入:无预期输出:News [id=1, title=岳云鹏的18岁,贾玲的18岁,沈腾的18岁,网友:不是来搞笑的?, anthor_name=光明网]说明:测试文件中会向news表中插入二条新闻数据,以便对你编写的方法进行检测,数据如下:idtitleauthor_name1岳云鹏的18岁,贾玲的18岁,沈腾的18岁,网友:不是来搞笑的?小明2假设飞行器每秒跑1光年,能飞到宇宙边缘吗?科学家说出答案探索宇宙奥妙开始你的任务吧,祝你成功!实现代码【JDBCUtils.java】package step1; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import test.News; public class JDBCUtils { /** * 连接数据库 */ private static Connection getConnection() { Connection conn = null; /********** Begin **********/ String url = "jdbc:mysql://localhost:3306/mysql_db"; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection(url, "root", "123123"); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } /********** End **********/ return conn; } /** * 更新数据方法 * * @param news * @throws SQLException */ public void update(News news) throws SQLException { Connection conn = getConnection(); PreparedStatement ps = null; /********** Begin **********/ String sql = "update news set title = ? ,author_name = ? where id = ? "; try { ps = conn.prepareStatement(sql); ps.setObject(1, news.getTitle()); ps.setObject(2, news.getAuthor_name()); ps.setObject(3, news.getId()); ps.execute(); } catch (SQLException e) { e.printStackTrace(); throw new SQLException("更新数据失败"); } finally { close(null, ps, conn); } /********** End **********/ } /** * 查询所有数据 * * @return * @throws SQLException */ public List<News> findAll() throws SQLException { Connection conn = getConnection(); PreparedStatement ps = null; ResultSet rs = null; News news = null; List<News> newsList = new ArrayList<News>(); /********** Begin **********/ String sql = "select * from news"; try { ps = conn.prepareStatement(sql); rs = ps.executeQuery(); while (rs.next()) { news = new News(rs.getInt(1), rs.getString(2), rs.getString(3)); newsList.add(news); } } catch (SQLException e) { e.printStackTrace(); throw new SQLException("查询所有数据失败"); } finally { close(rs, ps, conn); } /********** End **********/ return newsList; } /** * 删除方法 * * @param id * @throws SQLException */ public void delete(int id) throws SQLException { Connection conn = getConnection(); PreparedStatement ps = null; /********** Begin **********/ String sql = "delete from news where id=?"; try { ps = conn.prepareStatement(sql); ps.setObject(1, id); ps.execute(); } catch (SQLException e) { e.printStackTrace(); throw new SQLException(" 删除数据失败"); } finally { close(null, ps, conn); } /********** End **********/ } /** * 增加对象 * * @param news * @throws SQLException */ public void insert(News news) throws SQLException { Connection conn = getConnection(); PreparedStatement ps = null; String sql = "insert into news(id,title,author_name)values(?,?,?)"; try { ps = conn.prepareStatement(sql); ps.setInt(1, news.getId()); ps.setString(2, news.getTitle()); ps.setString(3, news.getAuthor_name()); ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); throw new SQLException("添加数据失败"); } finally { close(null, ps, conn); } } /** * 根据id查询对象 * * @param id * @return * @throws SQLException */ public News findById(int id) throws SQLException { Connection conn = getConnection(); PreparedStatement ps = null; ResultSet rs = null; News news = null; String sql = "select * from news where id=?"; try { ps = conn.prepareStatement(sql); ps.setInt(1, id); rs = ps.executeQuery(); if (rs.next()) { news = new News(); news.setId(id); news.setTitle(rs.getString(2)); news.setAuthor_name(rs.getString(3)); } } catch (SQLException e) { e.printStackTrace(); throw new SQLException("根据ID查询数据失败"); } finally { close(rs, ps, conn); } return news; } /** * 关闭数据库连接 * * @param rs * @param ps * @param conn */ public static void close(ResultSet rs, PreparedStatement ps, Connection conn) { try { if (rs != null) rs.close(); if (ps != null) ps.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }【News.java】package test; public class News { private int id; private String title; private String author_name; public News() { super(); } public News(int id, String title, String anthor_name) { super(); this.id = id; this.title = title; this.author_name = anthor_name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthor_name() { return author_name; } public void setAuthor_name(String author_name) { this.author_name = author_name; } @Override public String toString() { return "News [id=" + id + ", title=" + title + ", author_name=" + author_name + "]"; } }【Test1.java】package test; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.List; import step1.JDBCUtils; public class Test1 { public static void main(String[] args) throws SQLException { createTable(); JDBCUtils jUtils = new JDBCUtils(); News news1 = new News(1, "岳云鹏的18岁,贾玲的18岁,沈腾的18岁,网友:不是来搞笑的?", "小明"); News news2 = new News(2, "假设飞行器每秒跑1光年,能飞到宇宙边缘吗?科学家说出答案", "探索宇宙奥秘"); jUtils.insert(news1); jUtils.insert(news2); // 修改数据 news1.setAuthor_name("光明网"); jUtils.update(news1); // 删除数据 jUtils.delete(2); // 获取表中数据 List<News> findAll = jUtils.findAll(); for (int i = 0; i < findAll.size(); i++) { System.out.println(findAll.get(i)); } // 检测update方法 News findById = jUtils.findById(1); if (!findById.toString().equals(news1.toString())) { System.out.println("数据修改失败,请检查update方法"); System.out.println("查询id为1的结果:" + findById); } // 检查delete方法 News findById2 = jUtils.findById(2); if (findById2 != null) { System.out.println("数据删除失败,请检查delete方法"); System.out.println("查询id为2的结果:" + findById2); } } public static void createTable() { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } Connection conn = null; Statement statement = null; String url = "jdbc:mysql://localhost:3306/"; try { conn = DriverManager.getConnection(url, "root", "123123"); statement = conn.createStatement(); statement.executeUpdate("drop database if exists mysql_db"); statement.executeUpdate("create database mysql_db"); statement.executeUpdate("use mysql_db"); String sql = "create table news(" + "id int primary key, " + "title varchar(60), " + "author_name varchar(30)" + ")"; statement.executeUpdate(sql); } catch (SQLException e) { e.printStackTrace(); } finally { try { if (statement != null) statement.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }第2关:泛型JDBC封装任务描述本关任务:封装一个泛型类的JDBC工具类。相关知识上一章节中我们对具体类(News)进行JDBC的封装,但是在一个项目中,数据库中的表远不止一个,难道我们需要对每一张表都进行封装吗?显然是不妥的,因此我们可以将JDBC工具类封装为一个泛型的。在学习了反射( JAVA的重要机制——反射Reflection )之后,我们都知道反射机制的强大,利用反射可以获取到类的结构信息,动态调用属性和方法等等。因此,本章节我们采用反射对JDBC的增删改查进行泛型的封装。为了完成本关任务,你需要掌握:反射的常用方法。反射的常用方法获取Class的实例的三种方法Class class=类名.class; Class class=Class.forName("全类名"); Class class=对象.getClass();获取对象的类名String className=class.getName();//获取结果为全类名 String className=class.getSimpleName();//获取简单类名获取FieldField field=class.getField("属性名");//通过属性名获取public的属性 Field[] fields=class.getFields();//获取所有用public修饰的属性 Field field=class.getDeclaredField("属性名");//获取的属性包括public和private Field[] field = c.getDeclaredFields();//获取所有属性包括public和private获取Field的信息String name=field.getName();//获取属性名 Class<?> type=filed.getType();//获取属性类型 Object value=field.get(obj);//获取obj对象field属性的值 field.set(obj,value);//设置obj对象的field属性的值设置private修饰的属性为可访问field.setAccessible(true);//默认为false只能对public修饰的操作,设置为true可对private修饰的操作更新数据的泛型封装分析及实现我们可以从sql语句来进行分析,更新数据的sql,大家都不陌生:update 表名 set column2=value2,columen3=value3 where column1=value1;观察sql语句我们可以将该方法设计为让用户传入一个Object对象,然后利用反射获取对象中的所有属性对其进行修改,具体实现如下(注意Object对象属性名称要求和数据库中表结构字段名以及类型一致):public static void update(Object obj) { Connection conn = getConnection();//获取连接对象 PreparedStatement ps = null; Class<?> c = obj.getClass();//获取obj的Class StringBuffer sb = new StringBuffer("update "+ c.getSimpleName() +" set ");//利用StringBuffer进行修改SQL语句的构造 Field[] field = c.getDeclaredFields();//通过反射获取对象的属性数组 for(int i = 1; i < field.length; i++) { if(i != field.length-1) { //判断是否为最后一个属性,若不是则后增加逗号 sb.append(field[i].getName()).append("=?,"); }else { //若为最后一个属性则添加 where sb.append(field[i].getName()).append("=? where "); } } //默认第一个属性为主键,切更改时通过第一个属性进行更改 sb.append(field[0].getName() + "=?"); try { ps = conn.prepareStatement(sb.toString()); for(int i = 1; i < field.length; i++) { field[i].setAccessible(true);//设置可以访问私有属性 ps.setObject(i, field[i].get(obj));//对预编译的SQL语句中的 ? 进行赋值 } field[0].setAccessible(true); ps.setObject(field.length, field[0].get(obj)); ps.execute();//执行sql语句 } catch (Exception e) { e.printStackT\frace(); }finally { close(null,ps,conn);//关闭连接数据 } }编程要求根据更新数据的示例,在右侧编辑器补充代码,完成增加数据、删除数据、查询表中所有数据三个方法。测试说明测试输入:无预期输出:Student [id=2, name=李四, sex=男, age=20]News [id=1, title=岳云鹏的18岁,贾玲的18岁,沈腾的18岁,网友:不是来搞笑的?, author_name=光明网]News [id=2, title=假设飞行器每秒跑1光年,能飞到宇宙边缘吗?科学家说出答案, author_name=探索宇宙奥秘]平台将会根据已有的Student表和News表来调用你所编写的方法,进行数据的增删改查,分别为二张表中插入二条数据载进行修改News 表中一条数据,删除Student表中一条数据,插入数据如下:Student表idnamesexage1张三男182李四女20News表idtitleauthor_name1岳云鹏的18岁,贾玲的18岁,沈腾的18岁,网友:不是来搞笑的?光明网2假设飞行器每秒跑1光年,能飞到宇宙边缘吗?科学家说出答案小明注意:本章实训封装的JDBC工具类只做参考,你也可根据自己的实际需求进行封装属于自己的工具类。开始你的任务吧,祝你成功!实现代码【JDBCUtils.java】package step2; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class JDBCUtils { private static Connection getConnection() { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } String url = "jdbc:mysql://localhost:3306/mysql_db"; Connection conn = null; try { conn = DriverManager.getConnection(url, "root", "123123"); } catch (SQLException e) { e.printStackTrace(); } return conn; } /** * 类名对应表,属性对应字段 * * @param obj 传入的对象 * @return */ public void insert(Object obj) { Connection conn = getConnection(); // 连接数据库 PreparedStatement ps = null; /********** Begin **********/ try { String sql = "insert into "; String sql2 = ") values("; // 获取类名当表名 String simpleName = obj.getClass().getSimpleName(); sql += simpleName + "("; // 获取所有属性 Field[] declaredFields = obj.getClass().getDeclaredFields(); // 拼接sql语句 for (int i = 0; i < declaredFields.length; i++) { sql += declaredFields[i].getName(); sql2 += "?"; if (i + 1 != declaredFields.length) { sql += ","; sql2 += ","; } else { sql2 += ")"; } } sql += sql2; ps = conn.prepareStatement(sql); // 填充占位符 for (int i = 0; i < declaredFields.length; i++) { declaredFields[i].setAccessible(true); Object object = null; try { object = declaredFields[i].get(obj); } catch (Exception e) { e.printStackTrace(); } ps.setObject(i + 1, object); } ps.execute(); } /********** End **********/ catch (SQLException e) { e.printStackTrace(); } finally { close(null, ps, conn); } } /** * 通过对象的Class获取对应表中的所有记录 * * @param c * @return */ public <T> List<T> selectAll(Class<T> c) { Connection conn = getConnection(); List<T> list = new ArrayList<T>(); PreparedStatement ps = null; ResultSet rs = null; /********** Begin **********/ try { // 获取类名当作表名 String simpleName = c.getSimpleName(); // sql查询语句 String sql = "select * from " + simpleName; ps = conn.prepareStatement(sql); // 获取结果集 rs = ps.executeQuery(); // 获取所有属性,对应结果集的所有列 Field[] fields = c.getDeclaredFields(); while (rs.next()) { // 创建对象 T t = c.newInstance(); // 给对象对应的属性赋值 for (Field field : fields) { field.setAccessible(true); field.set(t, rs.getObject(field.getName())); } // 对象添加到集合 list.add(t); } } /********** End **********/ catch (Exception e) { e.printStackTrace(); } finally { close(rs, ps, conn); } return list; } /** * 通过主键(默认第一个属性)删除对象 * * @param obj * @return */ public void delete(Object obj) { Connection conn = getConnection(); PreparedStatement ps = null; /********** Begin **********/ try { // 获取类名当作表明 String simpleName = obj.getClass().getSimpleName(); // 获取第一个属性的信息 Field[] declaredFields = obj.getClass().getDeclaredFields(); declaredFields[0].setAccessible(true); String name = declaredFields[0].getName(); // sql删除语句 String sql = "delete from " + simpleName + " where " + name + "=?"; // 填充占位符 ps = conn.prepareStatement(sql); ps.setObject(1, declaredFields[0].get(obj)); ps.execute(); } /********** End **********/ catch (Exception e) { e.printStackTrace(); } finally { close(null, ps, conn); } } /** * 模拟jdbc的更新操作,默认第一个属性为主键 * * @param obj * @return */ public void update(Object obj) { Class<?> c = obj.getClass();// 获取obj的Class StringBuffer sb = new StringBuffer("update " + c.getSimpleName() + " set ");// 利用StringBuffer进行修改SQL语句的构造 Field[] field = c.getDeclaredFields();// 通过反射获取对象的属性数组 for (int i = 1; i < field.length; i++) { if (i != field.length - 1) { // 判断是否为最后一个属性,若不是则后增加逗号 sb.append(field[i].getName()).append("=?,"); } else { // 若为最后一个属性则添加 where sb.append(field[i].getName()).append("=? where "); } } // 默认第一个属性为主键,切更改时通过第一个属性进行更改 sb.append(field[0].getName() + "=?"); String sql = sb.toString() + ";"; Connection conn = getConnection();// 获取连接对象 PreparedStatement ps = null; try { ps = conn.prepareStatement(sql); for (int i = 1; i < field.length; i++) { field[i].setAccessible(true);// 设置可以访问私有属性 ps.setObject(i, field[i].get(obj));// 对预编译的SQL语句中的 ? 进行赋值 } field[0].setAccessible(true); ps.setObject(field.length, field[0].get(obj)); ps.execute();// 执行sql语句 } catch (Exception e) { e.printStackTrace(); } finally { close(null, ps, conn);// 关闭连接数据 } } public static void close(ResultSet rs, PreparedStatement ps, Connection conn) { try { if (rs != null) rs.close(); if (ps != null) ps.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } public <T> Object selectById(Class<T> c, int id) { String sql = "select * from " + c.getSimpleName() + " where id=" + id; Field[] field = c.getDeclaredFields(); Connection conn = getConnection(); PreparedStatement ps = null; ResultSet rs = null; Object obj = null; try { ps = conn.prepareStatement(sql); rs = ps.executeQuery(); obj = c.newInstance(); while (rs.next()) { for (int i = 0; i < field.length; i++) { field[i].setAccessible(true); field[i].set(obj, rs.getObject(field[i].getName())); } } } catch (Exception e) { e.printStackTrace(); } finally { close(rs, ps, conn); } return obj; } }【News.java】package test; public class News { private int id; private String title; private String author_name; public News() { super(); } public News(int id, String title, String anthor_name) { super(); this.id = id; this.title = title; this.author_name = anthor_name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthor_name() { return author_name; } public void setAuthor_name(String author_name) { this.author_name = author_name; } @Override public String toString() { return "News [id=" + id + ", title=" + title + ", author_name=" + author_name + "]"; } }【Test2.java】package test; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.List; import step2.JDBCUtils; public class Test2 { static JDBCUtils jUtils = new JDBCUtils(); public static void main(String[] args) { createTable(); Student student1 = new Student(1, "张三", "男", 18); Student student2 = new Student(2, "李四", "女", 20); News news1 = new News(1, "岳云鹏的18岁,贾玲的18岁,沈腾的18岁,网友:不是来搞笑的?", "光明网"); News news2 = new News(2, "假设飞行器每秒跑1光年,能飞到宇宙边缘吗?科学家说出答案", "小明"); jUtils.insert(student1); jUtils.insert(student2); jUtils.insert(news1); jUtils.insert(news2); isTrue(student1, student2, news1, news2, "新增"); news2.setAuthor_name("探索宇宙奥秘"); jUtils.update(news2); student2.setSex("男"); jUtils.update(student2); isTrue(student1, student2, news1, news2, "更新"); jUtils.delete(student1); List<Student> studentList1 = jUtils.selectAll(Student.class); if (studentList1.size() != 0) { if (studentList1.size() != 1) { System.out.println("数据删除失败,请检查delete方法。" + student1.toString() + "未被删除成功"); } } else { System.out.println("请完成查询方法"); } List<Student> studentList = jUtils.selectAll(Student.class); for (int i = 0; i < studentList.size(); i++) { System.out.println(studentList.get(i)); } List<News> newsList = jUtils.selectAll(News.class); for (int i = 0; i < newsList.size(); i++) { System.out.println(newsList.get(i)); } } public static void createTable() { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } Connection conn = null; Statement statement = null; String url = "jdbc:mysql://localhost:3306/"; try { conn = DriverManager.getConnection(url, "root", "123123"); statement = conn.createStatement(); statement.executeUpdate("drop database if exists mysql_db"); statement.executeUpdate("create database mysql_db"); statement.executeUpdate("use mysql_db"); String sql = "create table News(" + "id int primary key, " + "title varchar(60), " + "author_name varchar(30)" + ")"; statement.executeUpdate(sql); String sql2 = "create table Student(" + "id int primary key, " + "name varchar(20), " + "sex varchar(4)," + "age int" + ")"; statement.executeUpdate(sql2); } catch (SQLException e) { e.printStackTrace(); } finally { try { if (statement != null) statement.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static void isTrue(Student student1, Student student2, News news1, News news2, String type) { Student s1 = (Student) jUtils.selectById(Student.class, 1); Student s2 = (Student) jUtils.selectById(Student.class, 2); News n1 = (News) jUtils.selectById(News.class, 1); News n2 = (News) jUtils.selectById(News.class, 2); if (!student1.toString().equals(s1.toString()) || !student2.toString().equals(s2.toString()) || !news1.toString().equals(n1.toString()) || !news2.toString().equals(n2.toString())) { System.out.println("请按要求完成" + type + "方法"); } } }
第1关:JDBC连接数据库任务描述本关任务:使用jdbc连接数据库并完成创建数据库和创建表的操作。相关知识JDBC API提供以下接口和类:DriverManager:此类管理数据库驱动程序列表。可在JDBC下识别某个子协议的第一个驱动程序,用于建立数据库连接。Driver:此接口处理与数据库服务器的通信。我们很少会直接与Driver对象进行交互。在编程中要连接数据库,必须先装载特定厂商的数据库驱动程序。Connection:此接口具有用于联系数据库的所有方法。 Connection对象表示通信上下文,即与数据库的所有通信仅通过连接对象。Statement:用于执行静态SQL语句并返回它所生成结果的对象。一些派生接口还可接受参数,如PrepareStatement。ResultSet:提供检索不同类型字段的方法。(操作对象为Statement执行SQL查询后的结果)SQLException:此类处理数据库应用程序中发生的任何错误。使用JDBC的步骤如下:加载数据库驱动 → 建立数据库连接(Connection) → 创建执行SQL语句的Statement对象 → 处理执行结果(ResultSet) → 释放资源为了完成本关任务,你需要掌握:1.如何加载数据库驱动;2.如何建立数据库连接;3.如何执行编写的SQL语句;4.释放资源。加载数据库驱动驱动加载是为了打开与数据库的通信通道。在注册驱动前我们需要装载特定厂商的数据库驱动程序,导入mysq-connector-java的jar包,方法是在项目中建立lib目录,在其下放入jar包。然后右键jar包 Build Path→Add to Build Path完成jar包导入。将jar包导入项目之后我们就开始注册驱动:Java加载数据库驱动通常是使用Class类的静态方法forName(),语法格式如下:Class.forName(String driverManager)示例:try { Class.forName("com.mysql.jdbc.Driver" ); } catch (ClassNotFoundException e) { e.printStackT\frace(); }如果加载成功,会将加载的驱动类注册给DriverManager;加载失败,会抛出ClassNotFoundException异常。建立连接成功加载完数据库驱动后,就可以建立数据库的连接了,使用DriverManager的静态方法getConnection()来实现。如下:Connection conn = DriverManager.getConnection(url, user, password);URL用于标识数据库的位置,通过URL地址告诉JDBC程序连接信息。若不存在数据库,只建立连接,URL的写法为:若存在数据库test,URL的写法为:其中localhost可以换成IP地址127.0.0.1,3306为MySQL数据库的默认端口号,user和password对应数据库的用户名和密码。执行编写的SQL语句连接建立完毕后,就可以使用Connection接口的createStatement()方法来获取Statement对象;并通过executeUpdate()方法来执行SQL语句。创建statement对象try { Statement statement = conn.createStatement(); } catch (SQLException e) { e.printStackT\frace(); }创建数据库try { String sql1="drop database if exists test"; String sql2="create database test"; statement.executeUpdate(sql1);//执行sql语句 statement.executeUpdate(sql2); } catch (SQLException e) { e.printStackT\frace(); }创建表try { statement.executeUpdate("use test");//选择在哪个数据库中操作 String sql = "create table table1(" + "column1 int not null, " + "column2 varchar(255)" + ")"; statement.executeUpdate(sql); } catch (SQLException e) { e.printStackT\frace(); }释放资源Jdbc程序运行完后,切记要释放程序在运行过程中创建的那些与数据库进行交互的对象,这些对象通常是ResultSet, Statement和Connection对象。特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。为确保资源释放代码能运行,资源释放代码一定要放在finally语句中。finally { try { if(statement!=null) statement.close(); if(conn!=null) conn.close(); } catch (SQLException e) { e.printStackT\frace(); } }编程要求在右侧编辑器补充代码,完成下列相应任务:加载数据库驱动;【平台数据库连接的用户(user)为root,密码(password)为123123】创建数据库mysql_db;创建表student。student表结构为:字段名类型备注约束idint学生id非空namevarchar(20)学生姓名无sexvarchar(4)学生性别无ageint学生年龄无测试说明平台会对你编写的代码进行测试:测试输入:无预期输出:id INT(11)name VARCHAR(20)sex VARCHAR(4)age INT(11)开始你的任务吧,祝你成功!实现代码package jdbc; import java.sql.*; public class jdbcConn { public static void getConn() { try { // 1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } /********** End **********/ /********** Begin **********/ Connection conn = null; Statement statement = null; try { // 2.建立连接并创建数据库和表 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/", "root", "123123"); String sql1 = "drop database if exists mysql_db;"; String sql2 = "create database mysql_db;"; statement = conn.createStatement(); statement.execute(sql1); statement.execute(sql2); statement.execute("use mysql_db"); String sql3 = "create table student(id int not null,name varchar(20),sex varchar(4),age int)"; statement.execute(sql3); } catch (Exception e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } finally { try { if (statement != null) statement.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }第2关:JDBC对表中数据的操作任务描述本关任务:使用JDBC完成数据插入和查询。相关知识为了完成本关任务,你需要掌握:1.在连接时如何指定数据库;2.向指定表中插入数据;3.遍历表中数据。指定数据库连接当我们已经有数据库时,可以直接在连接时指定数据库,如下指定与test_db数据库建立连接:String url = "jdbc:mysql://localhost:3306/test_db"; Connection conn = DriverManager.getConnection (url,"root","123123" );当在连接时指定数据库后,我们就不用编写SQL语句进行选择数据库了。向指定表中插入数据建立连接之后,编写向表中插入数据的sql语句,使用Statement对象的executeUpdate()方法来执行该sql语句就可向表中修改数据(该方法适用于insert、update、delete的sql语句),当sql语句为查询语句时,则使用executeQuery()方法:try { Statement statement = conn.createStatement(); statement.executeUpdate("insert into table1(column1,column2) values(101,'xxx')"); } catch (SQLException e) { e.printStackT\frace(); }PreparedStatement上述直接使用Statement向表中插入数据,操作中存在SQL注入危险,脱离上述表,如下例:String id = "5"; String sql = "delete from tablename where id=" + id; Statement st = conn.createStatement(); st.executeQuery(sql);//查询到表中将无数据 //如果用户传入的id为“5 or 1=1”,那么将删除表中的所有记录为预防这种情况的SQL注入,PreparedStatement 有效的防止sql注入(SQL语句在程序运行前已经进行了预编译,当运行时动态的把参数传给PreprareStatement,即使参数里有敏感字符如or '1=1'数据库也会作为参数的一个字段属性值来处理而不会作为一个SQL指令)PreparedStatement使用如下:PreparedStatement statement = conn.prepareStatement("insert into table1(column1,column2) values(?,?)");//使用占位符来先占个位置 statement.setInt(1,101);//占位符顺序从1开始,根据数据库中字段相应的类型存入数据 statement.setString(2, "XXX");//也可以使用setObject statement.executeUpdate();//每执行一个sql语句就需要执行该方法查询表中数据Jdbc程序中的ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式,ResultSet对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next()方法,可以使游标指向具体的数据行,然后调用方法获取该行的数据。//编写查询sql语句 PreparedStatement statement = conn.prepareStatement("select * from table1"); ResultSet resultSet = statement.executeQuery();//将执行结果给ResultSet while (resultSet.next()) {//循环判断表中是否还有数据 int id = resultSet.getInt(1);//通过列的索引查询 String name = resultSet.getString("column2");//通过列名查询 }编程要求在右侧编辑器补充代码,向上一章节中已创建好的数据库mysql_db中的表student中插入数据,并将插入的数据进行输出:idnamesexage1张三男192李四女183王五男20提示:已为你封装好student类,可在右侧文件夹中查看,此类可直接使用。测试说明平台会对你编写的代码进行测试:测试输入:无预期输出:1 张三 男 192 李四 女 183 王五 男 20开始你的任务吧,祝你成功!实现代码package jdbc; import java.sql.*; import java.util.ArrayList; import java.util.List; public class jdbcInsert { public static void insert() { /********** Begin **********/ try { // 加载驱动 Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } /********** End **********/ Connection conn = null; PreparedStatement statement = null; /********** Begin **********/ // 连接并插入数据 try { String url = "jdbc:mysql://localhost:3306/mysql_db?useUnicode=true&characterEncoding=utf8"; String user = "root"; String password = "123123"; conn = DriverManager.getConnection(url, user, password); String sql = "insert into student(id,name,sex,age) values (1,'张三','男',19),(2,'李四','女',18),(3,'王五','男',20)"; statement = conn.prepareStatement(sql); statement.executeUpdate(); String sql1 = "select * from student"; ResultSet rs = statement.executeQuery(sql1); Student student = null; while (rs.next()) { int id = rs.getInt(1); String name = rs.getString(2); String sex = rs.getString(3); int age = rs.getInt(4); student = new Student(id, name, sex, age); System.out.println( student.getId() + " " + student.getName() + " " + student.getSex() + " " + student.getAge()); } } catch (SQLException e) { e.printStackTrace(); } /********** End **********/ finally { try { if (statement != null) statement.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }第3关:JDBC事务任务描述本关任务:按照具体要求编写程序。相关知识为了完成本关任务,你需要掌握:1. 什么是事务;2. 事务的基本要素;3.如何开启事务;4.事务的提交和回滚。事务假设场景,我们有一个人员管理系统,你要删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就可以构成一个事务!事务能够控制何时更改提交并应用于数据库。 它将单个SQL语句或一组SQL语句视为一个逻辑单元,如果任何语句失败,整个事务将失败。事务的基本要素(ACID)原子性(Atomicity):一组事务,要么成功;要么撤回;一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到;隔离性(Isolation):事务独立运行。一个事务处理后的结果,影响了其他事务,那么其他事务会撤回。事务的100%隔离,需要牺牲速度;持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。开启事务开启事物需要启用手动事务支持,而不是使用JDBC驱动程序默认使用的自动提交模式,可调用Connection对象的setAutoCommit()方法。 如果将布尔的false传递给setAutoCommit(),则关闭自动提交,也就相当于开启了事物。 也可以传递一个布尔值true来重新打开它。Connection conn = DriverManager.getConnection (url,"root","123123" ); conn.setAutoCommit(false);//关闭自动提交开启事务提交和回滚在mysql数据库中,默认为将每一句sql都自动提交,当我们将它设置为手动事务支持(也就是已经手动开启事务)时,我们就可以在需要提交的时候进行手动提交:conn.commit();//提交事务当有多条sql语句一次提交时,我们需要考虑到其中sql是否合法,若其中某一条不合法,其他的sql是否仍需更改等一系列问题;确保事务的基本要素,我们需要手动调用事务回滚来控制sql的执行:try{ Connection conn = DriverManager.getConnection (url,"root","123123" ); conn.setAutoCommit(false);//开启事务 PreparedStatement ps = conn.prepareStatement("insert into table1(column1,column2) values(1,'xx1')"); ps.executeUpdate(); ps = conn.prepareStatement("insert in table1(column1,column2) values(1,'xx1')"); ps.executeUpdate(); conn.commit();//提交事务 } catch (SQLException e) { try { conn.rollback();//回滚事务 回滚到你开始事务之前 } catch (SQLException e1) { e1.printStackT\frace(); } }上述代码执行完毕后,数据库中并不会有数据更新。由于第二条insert语句语法错误,所以事务回滚,之前的insert也会失效。通常事务回滚都会放在catch中来捕获。开启事务后,一定要跟上 commit 或 rollback,及时释放可能锁住的数据。不用rollback()表面和用了rollback()效果一样,但是不用rollback()可能导致被锁住的数据不能及时的释放(需要等事物超时释放),会影响下一次的事物操作。编程要求根据提示,在右侧编辑器补充代码,编写一条新增SQL语句和任意一条错误的SQL语句,提交事务;要求第一条新增语句在数据库中被修改,其后错误SQL语句不执行。新增插入语句具体要求如下: 在mysql_db数据库student表中新增一条id为4,name为赵六,sex为女,age为21的数据。提示:每一条SQL语句之后都可提交事务。测试说明平台会对你编写的代码进行测试:测试输入:无预期输出:1 张三 男 192 李四 女 183 王五 男 204 赵六 女 21开始你的任务吧,祝你成功!实现代码package jdbc; import java.sql.*; public class jdbcTransaction { public static void transaction() { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } Connection conn = null; PreparedStatement ps = null; /********** Begin **********/ // 连接数据库并开启事务 try { String url = "jdbc:mysql://localhost:3306/mysql_db?useUnicode=true&characterEncoding=utf8"; conn = DriverManager.getConnection(url, "root", "123123"); conn.setAutoCommit(false); String sql = "insert into student(id,name,sex,age) values(4,'赵六','女',21)"; ps = conn.prepareStatement(sql); ps.executeUpdate(); conn.commit(); String sql1 = "daj;ljd"; ps.executeUpdate(); conn.commit(); String sql2 = "select * from student"; ResultSet rs = ps.executeQuery(sql2); Student student = null; while (rs.next()) { int id = rs.getInt(1); String name = rs.getString(2); String sex = rs.getString(3); int age = rs.getInt(4); student = new Student(id, name, sex, age); System.out.println( student.getId() + " " + student.getName() + " " + student.getSex() + " " + student.getAge()); } } catch (SQLException e) { try { // 事务回滚 conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } /********** End **********/ finally { try { if (ps != null) ps.close(); if (conn != null) conn.close(); } catch (SQLException e1) { e1.printStackTrace(); } } } }
之前一直用的是PicGo+GitHub搭建的图床,但是GitHub打开有点慢,而刚好在阿里云买了服务器,所以顺便用阿里云OSS搭建下图床,相比于GitHub来说,也不算贵,半年只需¥4.98,那么废话少说,开始了准备工作一个阿里云账号¥4.98第一步 下载安装并配置Typora下载进入官网选择版本下载即可官网:https://www.typora.io/安装一路默认安装即可第二步 下载PicGo Core打开【Typora】,点击左上角【文件】,【偏好设置】点击【图像】,选择【PicG0-Core(command line)】,点击【下载或更新】等待下载完,第二步就结束了第三步 登录阿里云,购买对象OSS存储访问阿里云,选择折扣套餐根据自身情况选择大小,进行购买购买完毕之后,点击进入管理控制台在概览页面,点击创建Bucket创建Bucket,填写名称和设置相关配置,Bucket名称后面会用到第四步 最后的配置打开【Typora】,点击左上角【文件】,【偏好设置】,点击【图像】,【打开配置文件】复制下面内容粘贴到打开的配置文件【config.json】{ "picBed": { "uploader": "aliyun", "aliyun": { "accessKeyId": "", "accessKeySecret": "", "bucket": "", // 存储空间名 "area": "", // 存储区域代号 "path": "img/", // 自定义存储路径 "customUrl": "", // 自定义域名,注意要加 http://或者 https:// "options": "" // 针对图片的一些后缀处理参数 PicGo 2.2.0+ PicGo-Core 1.4.0+ } }, "picgoPlugins": {} } 获取accessKeyId和accessKeySecret右上角【用户头像】,点击【AccessKey管理】创建新的AccessKey填入对应的accessKeyId和accessKeySecret获取”bucket”,“area“,”customUrl“点击进入管理控制台,点击【Bucket列表】,找到之前创建的Bucket名称并点击点击概览,填写对应的【config.json】文件例:第五步 上传测试验证图片上传上传成功则显示到这里PicGo Core+阿里云OSS搭建的图床就成功了
第1关:顺序输出任务描述本关任务:利用多线程相关知识控制三个线程,依照先后顺序顺序输出。相关知识(略)编程要求请仔细阅读右侧代码,在 Begin-End 区域内进行代码补充,使线程依照先后顺序依次输出JavaThread+线程名。提示:我们知道线程的执行结果是随机的,什么时候执行线程是看哪一个线程抢占到了CPU的资源,现在请你利用所学知识使多个线程开启之后依照先后顺序执行。测试说明测试输入:无;预期输出:JavaThreadAAJavaThreadBBJavaThreadCCJavaThreadAAJavaThreadBBJavaThreadCCJavaThreadAAJavaThreadBBJavaThreadCCJavaThreadAAJavaThreadBBJavaThreadCCJavaThreadAAJavaThreadBBJavaThreadCC开始你的任务吧,祝你成功!代码示例如果超了时间限制,加一下System.exit(0)停止就行package step1; public class Task { public static void main(String[] args) throws Exception { /********* Begin *********/ // 在这里创建线程, 开启线程 Object a = new Object(); Object b = new Object(); Object c = new Object(); // 在这里创建线程, 开启线程 MyThread th1 = new MyThread("AA", a, c); MyThread th2 = new MyThread("BB", c, b); MyThread th3 = new MyThread("CC", b, a); th1.start(); Thread.sleep(10); th2.start(); Thread.sleep(10); th3.start(); Thread.sleep(10); System.exit(0); /********* End *********/ } } class MyThread extends Thread { /********* Begin *********/ String threadName; Object a = null; Object b = null; public MyThread(String threadName, Object a, Object b) { super(); this.threadName = threadName; this.a = a; this.b = b; } public synchronized void run() { int count = 5; while (count > 0) { synchronized (a) { synchronized (b) { System.out.println("Java Thread" + this.threadName); count--; b.notify(); } try { a.wait(); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } } } /********* End *********/ }第2关:售票窗口任务描述本关任务:利用多线程技术,编写一个卖票的小程序,相关知识(略)编程要求目前有三个窗口同时出售20张票,需要你在右侧代码中的 Begin-End区域编写代码实现多个窗口售票的功能,具体要求如下:票数要使用同一个静态值;为保证不会出现卖出同一张票,使用java多线程同步锁(synchronized或lock)。解题思路:创建一个站台类Station,继承Thread,重写run方法,在run方法里面执行售票操作(即如果票没卖完就一直卖)!售票要使用同步锁:即有一个站台卖这张票时,其他站台要等这张票卖完!测试说明本关执行代码已经提前写好如下,不需要你重新编写运行代码: public static void main(String[] args) { //实例化站台对象,并为每一个站台取名字 Station station1=new Station(); Station station2=new Station(); Station station3=new Station(); // 让每一个站台对象各自开始工作 station1.start(); station2.start(); station3.start(); }测试输入:无; 预期输出:卖出了第20张票卖出了第19张票卖出了第18张票卖出了第17张票卖出了第16张票卖出了第15张票卖出了第14张票卖出了第13张票卖出了第12张票卖出了第11张票卖出了第10张票卖出了第9张票卖出了第8张票卖出了第7张票卖出了第6张票卖出了第5张票卖出了第4张票卖出了第3张票卖出了第2张票卖出了第1张票票卖完了开始你的任务吧,祝你成功!代码示例需要注意的是监视器需要是一个共同的对象,不能是独立的,所以需要staticStation.javapackage step2; /********* Begin *********/ import java.util.concurrent.locks.ReentrantLock; //定义站台类,实现卖票的功能。 public class Station extends Thread { private static ReentrantLock lock = new ReentrantLock(); private static int ticket = 20; @Override public void run() { while (true) { try { lock.lock(); if (ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e1) { // TODO 自动生成的 catch 块 e1.printStackTrace(); } System.out.println("卖出了第" + ticket + "张票"); ticket--; } else { System.out.println("票卖完了"); System.exit(0); } } finally { lock.unlock(); } } } } /********* End *********/Task.javapackage step2; public class Task { public static void main(String[] args) { //实例化站台对象, Station station1=new Station(); Station station2=new Station(); Station station3=new Station(); // 让每一个站台对象各自开始工作 station1.start(); station2.start(); station3.start(); } }
第1关:线程的状态与调度任务描述本关任务:学习本关知识完成选择题。相关知识为了完成本关你需要掌握:1.线程的状态与调度;2.线程执行的优先级。线程的状态与调度如果看懂下图,你对线程的了解就会更上一层楼。当我们使用new关键字新建一个线程,这个时候线程就进入了新建状态(New),也就是图中未启动状态;调用start方法启动线程,这个时候就进入了可运行状态,也就是就绪状态(Runnable);就绪状态获取了CPU资源,开始执行run方法,就进入了运行状态(Running);阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种;等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁);同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中;其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁);死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。线程优先级我们知道,多个线程的执行是随机的,在一个时段执行哪个线程是看哪一个线程在此时拥有CPU的执行权限。在Java中线程有优先级,优先级高的线程会获得较多的运行机会。Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:static int MAX_PRIORITY 线程可以具有的最高优先级,取值为10。 static int MIN_PRIORITY 线程可以具有的最低优先级,取值为1。 static int NORM_PRIORITY 分配给线程的默认优先级,取值为5。如果要设置和获取线程的优先级,可以使用Thread类的setPriority()和getPriority()方法。每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。关于线程调度与优先级你还需要了解:线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。 线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。 线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。 线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。 线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。编程要求略 ####测试说明 略题1、有三种原因可以导致线程不能运行,它们是( ABC )A、等待B、阻塞C、休眠D、挂起及由于I/O操作而阻塞2、Java语言中提供了一个( D )线程,自动回收动态分配的内存。A、异步B、消费者C、守护D、垃圾收集3、当( A )方法终止时,能使线程进入死亡状态A、runB、setPriorityC、yieldD、sleep4、用( B )方法可以改变线程的优先级。A、runB、setPriorityC、yieldD、sleep5、线程通过( D )方法可以休眠一段时间,然后恢复运行A、runB、setPriorityC、yieldD、sleep6、下列关于线程的说法正确的是( ABD )A、如果线程死亡,它便不能运行B、在Java中,高优先级的可运行线程会抢占低优先级线程C、线程可以用yield方法使低优先级的线程运行D、一个线程可以调用yield方法使其他线程有机会运行第2关:常用函数(一)任务描述本关任务:获取子线程执行的结果并输出。相关知识2本关你需要掌握sleep与join函数的用法。sleep()函数sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。使用方式很简单在线程的内部使用Thread.sleep(millis)即可。sleep()使当前线程进入停滞状态(阻塞当前线程),让出CPU的使用,目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。join()函数join函数的定义是指:等待线程终止。我们在运行线程的时候可能会遇到,在主线程中运行子线程,主线程需要获取子线程最终执行结果的情况。但是有很多时候子线程进行了很多耗时的操作,主线程往往先于子线程结束,这个时候主线程就获取不到子线程的最终执行结果了。使用join函数,可以解决这一问题。我们通过两个示例来理解:不使用join函数的版本:class MyThread extends Thread { private String name; public MyThread(String name) { this.name = name; } public void run() { System.out.println("子线程开始运行"); for (int i = 0; i < 5; i++) { System.out.println("子线程" + name + "运行" + i); try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackT\frace(); } } System.out.println("子线程结束"); } } public class Test { public static void main(String[] args) { Thread t = new MyThread("子线程A"); t.start(); for (int i = 0; i < 5; i++) { System.out.println("主线程执行" + i); } System.out.println("主线程结束"); } }输出结果(每次都不一样):主线程执行0主线程执行1主线程执行2主线程执行3主线程执行4主线程结束子线程开始运行子线程子线程A运行0子线程子线程A运行1子线程子线程A运行2子线程子线程A运行3子线程子线程A运行4子线程结束可以发现每次运行,主线程都是先于子线程结束的。使用join函数:package step1; class MyThread extends Thread { private String name; public MyThread(String name) { this.name = name; } public void run() { System.out.println("子线程开始运行"); for (int i = 0; i < 5; i++) { System.out.println("子线程" + name + "运行" + i); try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackT\frace(); } } System.out.println("子线程结束"); } } public class Test { public static void main(String[] args) { Thread t = new MyThread("子线程A"); t.start(); for (int i = 0; i < 5; i++) { System.out.println("主线程执行" + i); } try { t.join(); } catch (Exception e) { e.printStackT\frace(); } System.out.println("主线程结束"); } }输出结果:主线程执行1主线程执行2主线程执行3子线程开始运行主线程执行4子线程子线程A运行0子线程子线程A运行1子线程子线程A运行2子线程子线程A运行3子线程子线程A运行4子线程结束主线程结束可以发现无论运行多少次,主线程都会等待子线程结束之后在结束。编程要求请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,具体任务如下:创建自定义线程,实现求第num项斐波那契数列的值num从0开始,并且在main函数中获取子线程最终计算的结果。测试说明补充完代码后,点击测评,平台会对你编写的代码进行测试,当你的结果与预期输出一致时,即为通过。输入:5输出:子线程计算结果为:5输入:8输出:子线程计算结果为:21输入:10输出:子线程计算结果为:55开始你的任务吧,祝你成功!代码示例package step2; import java.util.Scanner; public class Task { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int num = sc.nextInt(); // 请在此添加实现代码 /********** Begin **********/ MyThread mt = new MyThread(num); mt.start(); try { mt.join(); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } /********** End **********/ } } //请在此添加实现代码 /********** Begin **********/ class MyThread extends Thread { int num; public MyThread(int num) { this.num = num; } @Override public void run() { // TODO 自动生成的方法存根 System.out.println("子线程计算结果为:" + num(num)); } public int num(int n) { if (n < 3) { return 1; } else return num(n - 1) + num(n - 2); } } /********** End **********/第3关:常用函数(二)任务描述本关任务:使用三个线程交替打印5次EDU。相关知识为了完成本关任务你需要掌握:1.yield函数的用法;2.wait函数的用法;3.其他常用函数的使用。yield 函数yield函数可以理解为“让步”,它的作用是:暂停当前正在执行的线程对象,并执行其他线程。yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。示例:public class Task { public static void main(String[] args) { System.out.println(Thread.currentThread().getName()+"主线程运行开始!"); MyThread mTh1=new MyThread("A"); MyThread mTh2=new MyThread("B"); mTh1.start(); mTh2.start(); System.out.println(Thread.currentThread().getName()+ "主线程运行结束!"); } } class MyThread extends Thread { private String name; public MyThread(String name) { this.name = name; } public void run() { for (int i = 0; i < 50; i++) { System.out.println(name + "线程" + "执行" + i); if(i == 10){ this.yield(); } } } }运行结果有两种情况:第一种情况:A(线程)当执行到10时会让掉CPU时间,这时B(线程)抢到CPU时间并执行。第二种情况:B(线程)当执行到10时会让掉CPU时间,这时A(线程)抢到CPU时间并执行。我们还可以考虑在其中加入setPriority函数改变线程优先级从而改变线程的执行顺序。wait 函数要弄明白wait函数我们首先需要了解线程锁的概念。 线程锁:其实就像我们日常生活中的锁,如果一个房子上了锁,别人就进不去,在Java中也类似,如果一段代码取得了锁,那么其他地方就不能运行这段代码,只能等待锁的释放。这里我们会涉及到两个函数,1.wait(),2.notify()。这两个函数都是Object类自带的函数。在下面的例子中我们会使用synchronized(Obj)来实现线程同步,同步的概念后面会讲到,这里不理解没关系,不影响对于wait函数的理解。从功能上来说:wait()就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行;相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。纸上得来终觉浅,欲知此事须躬行,我们还是通过实例来学习这些函数的用法。问题:建立两个线程,A线程打印5次A,B线程打印5次B,要求线程同时运行,交替打印5次AB。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下:public class MyThread implements Runnable { private String name; private Object prev; private Object self; private MyThread(String name, Object prev, Object self) { this.name = name; this.prev = prev; this.self = self; } public void run() { int count = 5; while (count > 0) { synchronized (prev) { synchronized (self) { System.out.print(name); count--; self.notify(); } try { prev.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } System.exit(0);//退出jvm } public static void main(String[] args) throws Exception { Object a = new Object(); Object b = new Object(); MyThread ta = new MyThread("A", b, a); MyThread tb = new MyThread("B", a, b); new Thread(ta).start(); Thread.sleep(100); //确保按顺序A、B执行 new Thread(tb).start(); Thread.sleep(100); } }运行程序,结果为:ABABABABAB线程常用函数总结下表列出的是线程类常用的函数:函数描述public void start()使该线程开始执行;Java 虚拟机调用该线程的 run 方法。public void run()如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。public final void setName(String name)改变线程名称,使之与参数 name 相同。public final void setPriority(int priority)更改线程的优先级。public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。public final void join(long millisec)等待该线程终止的时间最长为 millis 毫秒public void interrupt()中断线程。public final boolean isAlive()测试线程是否处于活动状态。public static void yield()暂停当前正在执行的线程对象,并执行其他线程。public static void sleep(long millisec)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。public static boolean holdsLock(Object x)当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。public static Thread currentThread()返回对当前正在执行的线程对象的引用。public static void dumpStack()将当前线程的堆栈跟踪打印至标准错误流。编程要求请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,具体任务如下:建立三个线程,A线程打印5次E,B线程打印5次D,C线程打印5次U,要求线程同时运行,交替打印5次EDU。测试说明补充完代码后,点击测评,平台会对你编写的代码进行测试,当你的结果与预期输出一致时,即为通过。开始你的任务吧,祝你成功!代码示例package step3; public class MyThread implements Runnable { // 请在此添加实现代码 /********** Begin **********/ private String name; private Object prev; private Object self; private MyThread(String name, Object prev, Object self) { this.name = name; this.prev = prev; this.self = self; } public void run() { int count = 5; while (count > 0) { synchronized (prev) { synchronized (self) { System.out.print(name); count--; self.notify(); } try { prev.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } System.exit(0);// 退出jvm } public static void main(String[] args) throws Exception { Object a = new Object(); Object b = new Object(); Object c = new Object(); MyThread ta = new MyThread("E", c, b); MyThread tb = new MyThread("D", b, a); MyThread tc = new MyThread("U", a, c); new Thread(ta).start(); Thread.sleep(100); // 确保按顺序A、B执行 new Thread(tb).start(); Thread.sleep(100); new Thread(tc).start(); } /********** End **********/ }输出:EDUEDUEDUEDUEDU
第1关:单词分割任务描述本关任务:将一段英语字符串进行单词分割。相关知识为了完成本关任务,你需要掌握:如何将字符串进行分割。String.split()拆分字符串lang包String类的split()方法public String[] split(String regex) public String[] split(String regex,int limit) //limit 参数控制模式应用的次数,因此影响所得数组的长度拆分示例:public class SplitDemo { public static void main(String[] args) { String Str="Harry James Potter"; String[] StrArray=Str.split("\\s");//"\\s"表示空格 //也可以来" "来进行拆分 String[] StrArray=Str.split(" "); for(String str:StrArray){ System.out.println(str); } }运行结果HarryJamesPotterStringTokenizer类拆分字符串util包下的StringTokenizer类拆分原理StringTokenizer拆分字符串的原理是通过生成StringTokenizer对象,然后运用对象的属性来处理字符串拆分的。public StringTokenizer(String str,String delim,boolean returnDelims) public StringTokenizer(String str,String delim) public StringTokenizer(String str) //str:要解析的字符串 delim:分隔符 returnDelims:是否将分隔符作为标记返回拆分示例:import java.util.StringTokenizer; public class StringTokenDemo { public static void main(String[] args) { String Str="Harry James Potter"; StringTokenizer strToken=new StringTokenizer(Str); //当有拆分的子字符串时,输出这个字符串 while(strToken.hasMoreTokens()){ System.out.println(strToken.nextToken()); } } }运行结果HarryJamesPotter编程要求请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,具体任务如下:用String.split()方法将字符串“aaa|bbb|ccc”以“|”进行拆分,用StringTokenizer类将字符串“This?is?a?test?string”以“?”进行拆分。测试说明补充完代码后,点击测评,平台会对你编写的代码进行测试,当你的结果与预期输出一致时,即为通过。测试输入:aaa|bbb|cccThis?is?a?test?string预期输出:aaabbbcccThisisateststring提示: “|”、“.”、“*”、“+”、“\\”等不是有效的模式匹配规则表达式,是转义字符,使用split()方法时必须得加"\\"才行。开始你的任务吧,祝你成功!代码示例test.javapackage step1; import java.util.List; import java.util.ArrayList; import java.util.Scanner; public class test{ public static void main(String[] args){ Scanner sc=new Scanner(System.in); StudentDemo demo=new StudentDemo(); String str1=sc.nextLine(); String str2=sc.nextLine(); List<String> list1=demo.splitPartition(str1); List<String> list2=demo.tokenPartition(str2); for(String s:list1){ System.out.println(s); } System.out.println(); for(String s:list2){ System.out.println(s); } } }StudentDemo.javapackage step1; import java.util.List; import java.util.ArrayList; import java.util.StringTokenizer; public class StudentDemo { // 使用String.split()方法分割 public List<String> splitPartition(String str) { List<String> list = new ArrayList<String>(); //请在此添加实现代码 /********** Begin **********/ String[] strings = str.split("\\|"); for (String string : strings) { list.add(string); } /********** End **********/ return list; } // 使用StringTokenizer类进行分割 public List<String> tokenPartition(String str) { List<String> list = new ArrayList<String>(); // 请在此添加实现代码 /********** Begin **********/ StringTokenizer strings = new StringTokenizer(str, "?"); while (strings.hasMoreTokens()) { list.add(strings.nextToken()); } /********** End **********/ return list; } }
编程要求根据上述步骤,配置好Hadoop开发环境,点击评测即可。提示:如果出现文件解压大小限制的情况,可以使用 ulimit -f 1000000 命令来解除限制。开始你的任务吧,祝你成功!实现代码判断JDK有没有安装:输入 echo $JAVA_HOME就可以看到哦窗口出现则表示JDK已经安装,可以进行下面的步骤。如果没有安装,则先进行第一关的代码安装JDK代码:cd /opt tar -zxvf hadoop-3.1.0.tar.gz -C /app cd /app mv hadoop-3.1.0/ hadoop3.1提示:如果出现文件解压大小限制的情况,可以使用 ulimit -f 1000000 命令来解除限制。ssh-keygen -t rsa -P '' 回车 cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys vim /etc/ssh/sshd_config在文件中找到这三个找到之后,移动光标i删掉开头的 # 和下图内容相同cd /app/hadoop3.1/etc/hadoop/ vim hadoop-env.sh i # The java implementation to use. #export JAVA_HOME=${JAVA_HOME} export JAVA_HOME=/app/jdk1.8.0_171 按esc键 冒号,shift+: wqvim yarn-env.sh i export JAVA_HOME=/app/jdk1.8.0_171 按esc键 冒号,shift+: wqvim core-site.xml 在文件末尾的configuration标签中添加代码如下: <property> <name>fs.default.name</name> <value>hdfs://localhost:9000</value> <description>HDFS的URI,文件系统://namenode标识:端口号</description> </property> <property> <name>hadoop.tmp.dir</name> <value>/usr/hadoop/tmp</value> <description>namenode上本地的hadoop临时文件夹</description> </property> 按esc键 冒号,shift+: wqvim mapred-site.xml 在文件末尾的configuration标签中添加代码如下: <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> 按esc键 冒号,shift+: wqvim yarn-site.xml 在文件末尾的configuration标签中添加代码如下: <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.resourcemanager.webapp.address</name> <value>192.168.2.10:8099</value> <description>这个地址是mr管理界面的</description> </property> 按esc键 冒号,shift+: wqmkdir -p /usr/hadoop/tmp mkdir /usr/hadoop/hdfs mkdir /usr/hadoop/hdfs/data mkdir /usr/hadoop/hdfs/name vim /etc/profile 文件末尾插入以下代码: #set Hadoop Enviroment export HADOOP_HOME=/app/hadoop3.1 export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin 按esc键 冒号,shift+: wq source /etc/profilehadoop namenode -format cd /app/hadoop3.1/sbin vim start-dfs.sh i 回车键换行 在文件顶部添加以下参数: #!/usr/bin/env bash HDFS_DATANODE_USER=root HADOOP_SECURE_DN_USER=hdfs HDFS_NAMENODE_USER=root HDFS_SECONDARYNAMENODE_USER=root 按esc键 冒号,shift+: wqvim stop-dfs.sh i 回车键换行 在文件顶部添加以下参数: #!/usr/bin/env bash HDFS_DATANODE_USER=root HADOOP_SECURE_DN_USER=hdfs HDFS_NAMENODE_USER=root HDFS_SECONDARYNAMENODE_USER=root 按esc键 冒号,shift+: wq vim start-yarn.sh i 回车键换行 在文件顶部添加以下参数: #!/usr/bin/env bash YARN_RESOURCEMANAGER_USER=root HADOOP_SECURE_DN_USER=yarn YARN_NODEMANAGER_USER=root 按esc键 冒号,shift+: wq vim stop-yarn.sh i 回车键换行 在文件顶部添加以下参数: #!/usr/bin/env bash YARN_RESOURCEMANAGER_USER=root HADOOP_SECURE_DN_USER=yarn YARN_NODEMANAGER_USER=root 按esc键 冒号,shift+: wq start-dfs.sh jps出现以下内容则成功,快测评吧
第2关:配置开发环境 - Hadoop安装与伪分布式集群搭建任务描述本关任务:安装配置Hadoop开发环境。相关知识下载Hadoop我们去官网下载:http://hadoop.apache.org/ 在平台上已经帮你下载好了(在/opt目录下),这里只是展示一下下载步骤。输入wget下载Hadoop;wget http://mirrors.tuna.tsinghua.edu.cn/apache/hadoop/common/hadoop-2.7.7/hadoop-2.7.7.tar.gz如果是生产环境就需要验证文件的完整性,在这里就偷个懒了。由于解压包有大概300M,所以我们已经预先帮你下载好了,切换到/opt目录下即可看到。接下来解压Hadoop的压缩包,然后将解压好的文件移动到/app目录下。我们来切换到app目录下修改一下hadoop文件夹的名字。提示:如果出现文件解压大小限制的情况,可以使用 ulimit -f 1000000 命令来解除限制。配置Hadoop环境接下来我们开始配置Hadoop开发环境。 咱们来搭建一个单节点的集群,配置一个伪分布式,为什么不做分布式呢? 其实分布式的配置和伪分布式差不多,只是分布式机器增加了而已,其他没什么两样,所以作为Hadoop学习我们搭建伪分布式要更好一点,不过后期我们会搭建真正的分布式环境。 好了,开始吧。你可以和我一起来,也可以看官方文档:http://hadoop.apache.org/docs/r3.1.0/hadoop-project-dist/hadoop-common/SingleCluster.html设置SSH免密登录在之后操作集群的时候我们需要经常登录主机和从机,所以设置SSH免密登录时有必要的。输入如下代码: ssh-keygen -t rsa -P ''生成无密码密钥对,询问保存路径直接输入回车,生成密钥对:id_rsa和id_rsa.pub,默认存储在~/.ssh目录下。 接下来:把id_rsa.pub追加到授权的key里面去。cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys然后修改权限:chmod 600 ~/.ssh/authorized_keys接着需要启用RSA认证,启动公钥私钥配对认证方式:vim /etc/ssh/sshd_config 如果提示权限不足在命令前加上sudo; 修改ssh配置:RSAAuthentication yes # 启用 RSA 认证PubkeyAuthentication yes # 启用公钥私钥配对认证方式AuthorizedKeysFile %h/.ssh/authorized_keys # 公钥文件路径重启SSH(在本地自己的虚拟机中可以重启,在平台不可以重启哦,也不需要,重启了你就连接不上命令行了!)service ssh restart好了准备工作已经做完了,我们要开始修改Hadoop的配置文件了,总共需要修改6个文件。分别是:hadoop-env.sh;yarn-env.sh ;core-site.xml;hdfs-site.xml;mapred-site.xml;yarn-site.xml。我们一个一个接着来配置吧!hadoop-env.sh 配置两个env.sh文件主要是配置JDK的位置提示:如果忘记了JDK的位置了,输入 echo $JAVA_HOME就可以看到哦。首先我们切换到hadoop目录下cd /app/hadoop3.1/etc/hadoop/编辑 hadoop-env.sh在文件中插入如下代码:# The java implementation to use. #export JAVA_HOME=${JAVA_HOME} export JAVA_HOME=/app/jdk1.8.0_171yarn-env.sh 配置编辑yarn-env.sh 插入如下代码:export JAVA_HOME=/app/jdk1.8.0_171core-site.xml配置这个是核心配置文件我们需要在该文件中加入HDFS的URI和NameNode的临时文件夹位置,这个临时文件夹在下文中会创建。 在文件末尾的configuration标签中添加代码如下:<configuration> <property> <name>fs.default.name</name> <value>hdfs://localhost:9000</value> <description>HDFS的URI,文件系统://namenode标识:端口号</description> </property> <property> <name>hadoop.tmp.dir</name> <value>/usr/hadoop/tmp</value> <description>namenode上本地的hadoop临时文件夹</description> </property> </configuration> hdfs-site.xml文件配置replication指的是副本数量,我们现在是单节点,所以是1。<configuration> <property> <name>dfs.name.dir</name> <value>/usr/hadoop/hdfs/name</value> <description>namenode上存储hdfs名字空间元数据 </description> </property> <property> <name>dfs.data.dir</name> <value>/usr/hadoop/hdfs/data</value> <description>datanode上数据块的物理存储位置</description> </property> <property> <name>dfs.replication</name> <value>1</value> </property> </configuration> mapred-site.xml文件配置<configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property></configuration>yarn-site.xml配置<configuration> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.resourcemanager.webapp.address</name> <value>192.168.2.10:8099</value> <description>这个地址是mr管理界面的</description> </property> </configuration> 创建文件夹我们在配置文件中配置了一些文件夹路径,现在我们来创建他们,在/usr/hadoop/目录下使用hadoop用户操作,建立tmp、hdfs/name、hdfs/data目录,执行如下命令:mkdir -p /usr/hadoop/tmp mkdir /usr/hadoop/hdfs mkdir /usr/hadoop/hdfs/data mkdir /usr/hadoop/hdfs/name将Hadoop添加到环境变量中vim /etc/profile在文件末尾插入如下代码:最后使修改生效:source /etc/profile验证现在配置工作已经基本搞定,接下来只需要完成:1.格式化HDFS文件、2.启动hadoop、3.验证Hadoop 即可。格式化在使用Hadoop之前我们需要格式化一些hadoop的基本信息。 使用如下命令:hadoop namenode -format出现如下界面代表成功:启动Hadoop接下来我们启动Hadoop:start-dfs.sh • 1输入命令应该会出现如下图界面:这个是表示启动没成功,是因为root用户现在还不能启动hadoop,我们来设置一下就可以了。在/hadoop3.1/sbin路径下: cd /app/hadoop3.1/sbin。 将start-dfs.sh,stop-dfs.sh两个文件顶部添加以下参数#!/usr/bin/env bashHDFS_DATANODE_USER=rootHADOOP_SECURE_DN_USER=hdfsHDFS_NAMENODE_USER=rootHDFS_SECONDARYNAMENODE_USER=root还有,start-yarn.sh,stop-yarn.sh顶部也需添加以下:#!/usr/bin/env bashYARN_RESOURCEMANAGER_USER=rootHADOOP_SECURE_DN_USER=yarnYARN_NODEMANAGER_USER=root再次启动start-dfs.sh,最后输入命令 jps 验证,出现如下界面代表启动成功:之后如果你是图形化界面,可以在你虚拟机的图形化界面中打开火狐浏览器输入:http://localhost:9870/ 或者在windows机器上输入http://虚拟机ip地址:9870/ 也可以访问hadoop的管理页面。好了到了这一步Hadoop就安装完成了。
第1关:配置开发环境 - JavaJDK的配置任务描述本关任务:配置JavaJDK。相关知识配置开发环境是我们学习一门IT技术的第一步,Hadoop是基于Java开发的,所以我们学习Hadoop之前需要在Linux系统中配置Java的开发环境。下载JDK前往Oracle的官网下载JDK: https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html我们可以先下载到本地,然后从Windows中将文件传入到虚拟机中。 也可以复制链接地址,在Linux系统中下载,不过复制链接地址不能直接下载,因为Oracle做了限制,地址后缀需要加上它随机生成的随机码,才能下载到资源。所以我们可以点击下载,然后暂停,最后在下载管理中复制链接地址就可以在Linux系统中下载啦。因为JDK的压缩包有大概200M,所以我们已经在平台中为你下载好了JDK,不用你再去Oracle的官网去下载了,如果你要在自己的Linux系统中安装,那么还是需要下载的。我们已经将JDK的压缩包放在系统的/opt目录下了,在命令行中切换至该目录下即可。解压首先在右侧命令行中创建一个/app文件夹,我们之后的软件都将安装在该目录下。 命令:mkdir /app然后,切换到/opt目录下,来查看一下提供的压缩包。可以看到我们已经帮你下载好了JDK和Hadoop的安装文件。现在我们解压JDK并将其移动到/app目录下。tar -zxvf jdk-8u171-linux-x64.tar.gzmv jdk1.8.0_171/ /app • 1可以切换到/app目录下查看解压好的文件夹。配置环境变量解压好JDK之后还需要在环境变量中配置JDK,才可以使用,接下来就来配置JDK。 输入命令:vim /etc/profile 编辑配置文件;在文件末尾输入如下代码(不可以有空格):然后,保存并退出。最后:source /etc/profile使刚刚的配置生效。测试最后我们可以测试一下环境变量是否配置成功。 输入:java -version 出现如下界面代表配置成功。编程要求根据上述步骤完成Java开发环境的配置即可。注意:因为下次再开启实训,环境会重置,所以最好的方式是一次性通过所有关卡。开始配置JDK吧,go on。实现代码mkdir /app cd /opt tar -zxvf jdk-8u171-linux-x64.tar.gz mv jdk1.8.0_171/ /app vim /etc/profile 光标移动到文件末尾 i JAVA_HOME=/app/jdk1.8.0_171 CLASSPATH=.:$JAVA_HOME/lib/tools.jar PATH=$JAVA_HOME/bin:$PATH export JAVA_HOME CLASSPATH PATH 按esc键 冒号shift+: wq source /etc/profile 输入:java -version 出现如下界面代表配置成功。
第3关:文件查看器(递归实现)任务描述本关任务:实现一个文件查看器,要实现指定文件夹下所有文件以及文件夹目录结构的展示。相关知识可以使用isDirectory()方法来判断一个File对象是否是一个文件夹。示例:如果D://hello是一个文件夹,则输出true。编程要求请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,具体任务如下:实现对给定文件夹目录结构的展示,并以文件名按升序排序的形式打印至控制台。如果是文件夹则在其名字之前加上+--,若是文件则加上--,上级目录与下级目录、下级文件用两个空格作为间隔,补充完善右侧代码区中的showDirStructure(File dir)函数实现要求的功能,其中函数参数含义如下:1).dir:指定要显示的文件夹。注:main函数可点击右上角文件夹切换至本关Test.java文件进行查看。测试说明补充完代码后,点击测评,平台会对你编写的代码进行测试,当你的结果与预期输出一致时,即为通过。样例1输入:src/step3/root输出:样例2输入:src/step3/dir输出:提示:java中的File类有listFiles方法可以获取文件目录。开始挑战吧,祝你成功!实现代码package step3; import java.io.File; import java.util.Arrays; public class Task { /********** Begin **********/ String dir ="+--"; String wj = "--"; String level = ""; public void showDirStructure(File file) { //判断是否是文件夹 if(file.isDirectory()){ System.out.println(level+dir+file.getName()); String level1=level; level+=" "; File[] files = file.listFiles(); Arrays.sort(files); for (File file1 : files) { showDirStructure(file1); } level=level1; }else{ System.out.println(level+wj+file.getName()); } } /********** End **********/ } 第4关:图片查看器(第3关的基础上加筛选)挑战任务本关任务:小明想要开发一个图片查看器,但是他想只显示文件夹下所有图片类型的文件。你来帮小明实现这个功能吧。相关知识文件过滤器要从列表中排除扩展名为.SYS的所有文件,我们可以使用由功能接口FileFilter的实例表示的文件过滤器来实现。它包含一个accept()方法,它将File作为参数列出,如果应该列出文件,则返回true。返回false不会列出文件。以下代码创建一个文件过滤器,将过滤扩展名为.SYS的文件。FileFilter filter = file -> { if (file.isFile()) { String fileName = file.getName().toLowerCase(); if (fileName.endsWith(".sys")) { return false; } } return true; }; 下列代码展示如何使用一个过滤器:import java.io.File; import java.io.FileFilter; public class Main { public static void main(String[] args) { String dirPath = "C:\\"; File dir = new File(dirPath); // Create a file filter to exclude any .SYS file FileFilter filter = file -> { if (file.isFile()) { String fileName = file.getName().toLowerCase(); if (fileName.endsWith(".sys")) { return false; } } return true; }; File[] list = dir.listFiles(filter); for (File f : list) { if (f.isFile()) { System.out.println(f.getPath() + " (File)"); } else if (f.isDirectory()) { System.out.println(f.getPath() + " (Directory)"); } } } } 文件目录:过滤后打印的文件目录:编程要求请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,具体任务如下:实现对给定文件夹目录结构的展示,并以文件名按升序排序的形式打印至控制台。如果是文件夹则在其名字之前加上+--,若是文件则加上--,上级目录与下级目录、下级文件用两个空格作为间隔,另外需要对文件进行过滤,只显示图片类型的文件,本关需要过滤的图片文件类型有:“jpg,png,bmp”,请补充完善右侧代码区中的showDirStructure(File file)函数实现本关要求的功能,其中函数参数含义如下:1). file:指定要显示的文件夹。测试说明样例1输入:src/step4/root输出:样例2输入:src/step4/dir输出:开始挑战吧,祝你成功!实现代码package step4; import java.io.File; import java.io.FileFilter; import java.util.Arrays; public class Task { /********** Begin **********/ String dir ="+--"; String wj = "--"; String level = ""; public void showDirStructure(File file) { //判断是否是文件夹 if(file.isDirectory()){ System.out.println(level+dir+file.getName()); String level1=level; level+=" "; File[] files = file.listFiles(); Arrays.sort(files); for (File file1 : files) { showDirStructure(file1); } level=level1; }else{ //如果是文件,则筛选 String name = file.getName(); if(name.endsWith("jpg")||name.endsWith("bmp")||name.endsWith("png")) System.out.println(level+wj+name); } } /********** End **********/ }
第1关:创建文件任务描述本关任务:在指定文件夹下创建文件。相关知识本关你需要掌握:1.如何创建文件;2.如何判断文件是否存在。如何创建文件我们知道在Java中万物皆对象,所以用来操作文件的也应该是一个对象,它就是File类,在Java中使用File类来操作文件。如何创建一个文件呢?很简单,我们来看个例子:使用上面这段代码就可以在D盘下创建一个helloworld.txt文件了。如何判断文件是否存在如果一个文件已经存在,那我们一般不应该在去创建它,所以判断文件是否存在是我们需要知道的,如何来判断呢?运行上述代码,如果D://helloworld.txt已经存在则会输出true,否则输出false;编程要求请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,具体任务如下:在src/output目录下创建hello.txt,test.txt文件。需要先创建test.txt,后创建hello.txt。注意:文件操作有异常需要抛出。小贴士:在windows系统中文件目录表示方式为 D://XX.XX,在Linux中文件目录的表示方式为/xxdir/filename.txt ,在平台中使用的是Linux环境,所以编写代码的时候要使用Linux的目录方式哦。测试说明补充完代码后,点击测评,平台会对你编写的代码进行测试,当你的结果与预期输出一致时,即为通过。开始你的任务吧,祝你成功!实现代码package step1; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.util.Scanner; public class Task { /********* Begin *********/ public void solution()throws IOException { File file1 = new File("src/output/test.txt"); file1.createNewFile(); File file2 = new File("src/output/hello.txt"); file2.createNewFile(); } /********* End *********/ } 第2关:文件的常用操作任务描述本关任务:创建文件夹,删除文件,列出文件和目录。相关知识为了完成本关任务,你需要掌握:1.创建文件夹;2.删除文件;3.列出文件目录。创建文件夹这段代码就会在D盘下创建一个hello的文件夹。删除文件这段代码就可以删除D盘下的helloworld.txt文件列出文件夹下的文件如果要开发一个文件查看器,那么我们就需要能查看指定文件夹下的所有文件;Java中就提供了该方法。示例:D盘hello文件夹下文件如下:编写如下代码:输出结果:a.txtb.txttext.txt可以发现在Java中文件夹也是一个File对象,使用listFiles()方法可以获取该文件夹下所有文件的数组,使用getName()方法可以获取文件的文件名。编程要求请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,实现文件夹下所有文件信息的列出,删除指定文件夹,在文件夹下创建文件,具体要求如下:在src/下创建文件夹test2文件夹;删除src/output/下的test2.txt文件;在src/test2/目录下创建helloworld.txt文件和step2.txt文件;将src/output/目录和src/test2/目录下所有文件的文件名按升序排序并打印至控制台。提示:可以使用Arrays.sort()函数进行排序,使用Arrays类中的方法需要导入: import java.util.Arrays 。测试说明补充完代码后,点击测评,平台会对你编写的代码进行测试,当你的结果与预期输出一致时,即为通过。输出如下结果:output目录结构为:a.txtb.txtc.txthello.txttest.txttest2目录结构为:helloworld.txtstep2.txt开始你的任务吧,祝你成功!实现代码package step2; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.util.Arrays; public class Task { public static void dcFile() throws IOException { /********* Begin *********/ File file1 = new File("src/test2"); file1.mkdirs(); File file2 = new File("src/output/test2.txt"); file2.delete(); File file3 = new File("src/test2/helloworld.txt"); file3.createNewFile(); File file4 = new File("src/test2/step2.txt"); file4.createNewFile(); System.out.println("output目录结构为:"); File[] file5 = new File("src/output").listFiles(); Arrays.sort(file5); for (File file : file5) { System.out.println(file.getName()); } System.out.println("test2目录结构为:"); File[] file6 = new File("src/test2").listFiles(); Arrays.sort(file6); for (File file : file6) { System.out.println(file.getName()); } /********* End *********/ } }
2022年05月