基于Maven+ SpringMVC+ MyBatis +Druid+MySql员工管理系统新手教程
前言
百度上搜这个还能跳出来个100+RMB的课程。这课有好多人看。我看了一下目录,发现大概比我的系统全面。但是,如果你只是要使用这些工具搭建一个员工管理系统,看我的就够了。虽然我只是个新手,但是我实现了,并且尽我所能讲清楚代码。
项目目标
我们搭建的员工管理系统是要有员工,部门和员工,部门的关系和对前三者在应用场景下的增删改查。
具体来说,对于员工和部门的增删改查是较简单的。只需注意增时要保证员工或部门不存在,删时保证员工或部门存在,改时保证员工或部门存在并且查时能用名字或id查找。对于两者关系的增删改查稍微变了些东西。查的时候通过部门名字查找所有该部门的员工信息,包括员工个数并且这些员工和部门要存在且id一致。
工具介绍
Maven是用来下各种各样jar包的。
SpringMVC
这是个设计框架。主要由模块层(Model),视图层(View)和控制层(controller)组成。
模块层封装了数据与进行数据进行处理的代码,是实际经行数据处理的地方,也是与数据库交互的地方。我的代码里包括Service,ServiceImpl和Mapper来处理数据。其实,在写这个项目时,大可不必分这么多层,会显得徒增代码量,但是这是个要养成的好习惯,至少要知道分层思想。
视图层负责将应用显示给用户和显示模型的状态。这主要是前端干的事,和我后端无瓜。我们的视图由Navicat解决。
控制层负责视图和模型之间的交互,把用户的请求发送到对应的模型上,并把模型的改变及时反应到视图上。
Mysql
这是个数据库。结构从上到下依次是数据库(database)和表(table)。
Mybatis
我们java程序中存储的数据是对象,怎么把对象转化成mysql中的表呢?靠的就是mybatis。
Druid
这是一个数据传输时的连接池。用这个是为了数据传输时加速与省消耗。以前每次连接数据库都要建立联系,就像每次回家,都要买辆车;如果用连接池,它会把我们数据传输时用的放进去,要用时从里面拿就行了,就像共享单车。
具体的点之后和代码一起补充。
我使用的是IDEA(编程),Postman(调用接口)和Navicat(mysql数据可视化)。
其中Navicat还提供直接用Mysql命令操作数据库的功能。菜单里点击Query,再点击newQuery,就能直接写Mysql语句来验证程序里的命令是否正确。非常方便。
开始制作
创建Mysql表
如果你是在本地玩,需要在本地先起一个Mysql服务。我下了个5开头的版本。
https://dev.mysql.com/downloads/file/?id=506120
在Navicat上连接Mysql。先创建个Mysql的数据库。记住你的数据库名,之后要用。
Pom.xml
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> @Service @SpringBootApplication @MapperScan("/com.mybatis_druid_mysql.demo.Mapper") publicclassDemoApplication{ publicstaticvoidmain(String[]args){ SpringApplication.run(DemoApplication.class,args); } } 与以往不同,多了个@MapperScan。它在用到java配置时登记Mybatis的Mapper接口。 就像上文提及,druid像共享单车。那么我们就要规定各项指标,比如有多少单车,每条车可以骑多久等等。 application.properties配置文件 server.port=18001 spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.druid.url=(1) spring.datasource.druid.username=root spring.datasource.druid.password=password spring.datasource.druid.initial-size=5 spring.datasource.druid.max-active=20 spring.datasource.druid.min-idle=5 spring.datasource.druid.max-wait=60000 spring.datasource.druid.timeBetweenEvictionRunsMillis=60000 spring.datasource.druid.minEvictableIdleTimeMillis=300000 spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=200 第一行是我们程序的端口。 (1) 这里是你的mysql数据库的url,形如:jdbc:mysql://127.0.0.1/db_example?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false 其中,db_example是数据库的名字。?后面代表一些参数。 第五行开始就是一些各种各样的druid设置。 怎么样让程序调用上这些配置呢? packagecom.mybatis_druid_mysql.demo.Druid; importcom.alibaba.druid.pool.DruidDataSource; importorg.springframework.beans.factory.annotation.Value; importorg.springframework.boot.SpringBootConfiguration; importorg.springframework.context.annotation.Bean; importorg.springframework.context.annotation.PropertySource; importorg.springframework.transaction.annotation.EnableTransactionManagement; importjavax.sql.DataSource; @SpringBootConfiguration @PropertySource(value="classpath:/application.properties") @EnableTransactionManagement publicclassDruid{ @Value("${spring.datasource.druid.username}") privateStringuserName; @Value("${spring.datasource.druid.password}") privateStringpassword; @Value("${spring.datasource.druid.driver-class-name}") privateStringdriverName; @Value("${spring.datasource.druid.url}") privateStringurl; @Value("${spring.datasource.druid.initial-size}") privateintinitialSize; @Value("${spring.datasource.druid.max-active}") privateintmaxActive; @Value("${spring.datasource.druid.min-idle}") privateintminIdle; @Value("${spring.datasource.druid.max-wait}") privateintmaxWait; @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") privateinttimeBetweenEvictionRunsMillis; @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}") privateintminEvictableIdleTimeMillis; @Value("${spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize}") privateintmaxPoolPreparedStatementPerConnectionSize; @Bean(name="datasource") publicDataSourcedataSource(){//由druid-spring-boot-starter实现springboot提供的datasource接口 DruidDataSourcedataSource=null; try{ dataSource=newDruidDataSource(); dataSource.setUsername(userName); dataSource.setPassword(password); dataSource.setUrl(url); dataSource.setDriverClassName(driverName); dataSource.setInitialSize(initialSize); dataSource.setMaxActive(maxActive); dataSource.setMinIdle(minIdle); dataSource.setMaxWait(maxWait); dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize); }catch(Exceptione){ e.printStackTrace(); } returndataSource; } } @SpringBootConfiguration注释是一个类级别的注释,它指示此类提供了应用程序配置。 @PropertySource(value = "classpath:/application.properties")注释是一个读取properties配置文件属性的注释,塔指示了配置文件地址。 @EnableTransactionManagement注释是一个事务管理注解。 @Value注释是一个从属性源取值的注释,它后面写注释源的名称。 上面代码设置了dataSource。之后就会交给druid-spring-boot-starter这个依赖处理。 这样,Druid就连接完成啦! 实体(Entity)定义了我们数据的基本结构。比如,我们这里要做3张表:员工信息表,部门信息表和员工部门关系表。 @Entity(name="employee") @Data publicclassEmployee{ @Id Longid; Stringname; Stringsex; Stringaddress; } @Entity(name = "employee")注释说明这是一个实体,而且名字叫employee。 @Data注释说明这是一个数据,它代替了普通的getter和setter。 @Id注释说明这是这个实体的主键(primary key)。 Department @Entity(name="department") @Data publicclassDepartment{ @Id Longdepartment_id; Stringdepartment_name; } Connection @Entity(name="connection") @Data publicclassConnection{ @Id Longconnection_id; Longdepartment_id; Longemployee_id; Stringemployee_name; Stringdepartment_name; } 从端口调用接口到实现数据的增上改查(CRUD),是怎么实现的呢? 我的总共个分为这么几层:Controller->Service->ServiceImpl->Mapper。正规一点还要加上一层Dao层。 Controller @RestController @RequestMapping("/jerry") @Slf4j publicclassController{ @Autowired privateServiceservice; @PostMapping(value="/insertEmployee",produces=MediaType.APPLICATION_JSON_UTF8_VALUE, consumes=MediaType.APPLICATION_JSON_UTF8_VALUE) publicEmployeeinsertEmployee(@RequestBodyEmployeeemployee){ if(service.countEmployee(employee)==0) service.insertEmployee(employee); returnemployee; } @DeleteMapping("/deleteEmployee") publicStringdeleteEmployee(@RequestParamStringid){ service.deleteEmployee(id); return"deleted"; } @PutMapping(value="/updateEmployee",produces=MediaType.APPLICATION_JSON_UTF8_VALUE, consumes=MediaType.APPLICATION_JSON_UTF8_VALUE) publicEmployeeupdateEmployee(@RequestParamLongid,@RequestBodyEmployeeemployee){ employee.setId(id); service.updateEmployee(employee); log.info("result: {}",employee);//打印日志 returnemployee; } @GetMapping(value="/searchEmployee",produces=MediaType.APPLICATION_JSON_UTF8_VALUE) publicEmployeesearchEmployee(@RequestParam("id")Stringid){ returnservice.searchEmployee(id); } @PostMapping(value="/addDepartment",produces=MediaType.APPLICATION_JSON_UTF8_VALUE, consumes=MediaType.APPLICATION_JSON_UTF8_VALUE) @ResponseBody publicDepartmentaddDepartment(@RequestBodyDepartmentdepartment){ if(service.countDepartment(department)==0) service.addDepartment(department); returndepartment; } @DeleteMapping("/deleteDepartment") @ResponseBody publicStringdeleteDepartment(@RequestParamLongdepartment_id){ service.deleteDepartment(department_id); return"deleted"; } @GetMapping(value="/searchDepartment",produces=MediaType.APPLICATION_JSON_UTF8_VALUE) publicDepartmentsearchDepartment(@RequestParam("department_id")Stringdepartment_id){ log.info("result: {}",service.searchDepartment(department_id));//打印日志 Departmentdepartment=service.searchDepartment(department_id); returnservice.searchDepartment(department_id); } @PutMapping(value="/updateDepartment",produces=MediaType.APPLICATION_JSON_UTF8_VALUE, consumes=MediaType.APPLICATION_JSON_UTF8_VALUE) @ResponseBody publicDepartmentupdateDepartment(@RequestParamLongdepartment_id,@RequestBodyDepartmentdepartment){ department.setDepartment_id(department_id); service.updateDepartment(department); returndepartment; } @RequestMapping(value="/addConnection",produces=MediaType.APPLICATION_JSON_UTF8_VALUE, consumes=MediaType.APPLICATION_JSON_UTF8_VALUE) @ResponseBody publicConnectionaddConnection(@RequestBodyConnectionconnection){ if(service.countConnection(connection)==0&&(service.employeeExistAndIdCorrect(connection)==1 &&(service.departmentExistAndIdCorrect(connection)==1))) service.addConnection(connection); returnconnection; } @RequestMapping("/deleteConnection") @ResponseBody publicStringdeleteConnection(Longconnection_id){ service.deleteConnection(connection_id); return"deleted"; } @RequestMapping("/deleteEmployeeFromDepartment") @ResponseBody publicStringdeleteEmployeeFromDepartment(@RequestParam("employee_id")Longemployee_id,@RequestParam("department_id")Longdepartment_id){ service.deleteEmployeeFromDepartment(employee_id,department_id); return"deleted"; } @RequestMapping("/updateConnection") @ResponseBody publicStringupdateConnection(@RequestBodyConnectionconnection,@RequestParamLongconnection_id){ connection.setConnection_id(connection_id); service.updateConnection(connection); return"updated"; } @RequestMapping("/searchEmployeeFromDepartment") @ResponseBody publicList returnservice.searchEmployeeFromDepartment(department_name); } @RequestMapping("/searchDepartmentFromEmployee") @ResponseBody publicList returnservice.searchDepartmentFromEmployee(employee_name); } @RequestMapping("/countEmployeeFromDepartment") @ResponseBody publicLongcountEmployeeFromDepartment(Stringdepartment_name){ returnservice.countEmployeeFromDepartment(department_name); } } @RestController注释是一个连接了两种其他注释的注释,他们分别是@Controller和@ResponseBody,前者告诉系统这是个控制类,并且暗含@Component,能被系统扫描到;后者告诉系统这里返回的数据是以网络反应体(web response body)展示的,不是普通的返回数据。 @RequestMapping("/jerry")注释是一个引导网络请求到请求处理类的注释,它定义了调用接口时的路径。 @Slf4j注释是一个来自lombok依赖的注释,他能打印日志。 @Autowired注释是一个自动导航的注释,它能把@Component注释下的类纳为己用。 @PostMapping注释是@RequestMapping的一种,它要求请求时用Post方法调用。这种方法通常用在增删改查中的增。 @DeleteMapping注释是@RequestMapping的一种,它要求请求时用Delete方法调用。这种方法通常用在增删改查中的删。 @PutMapping注释是@RequestMapping的一种,它要求请求时用Put方法调用。这种方法通常用在增删改查中的改。 @GetMapping注释是@RequestMapping的一种,它要求请求时用Get方法调用。这种方法通常用在增删改查中的查。 以上四者都能定义请求调用时的路径和输入输出的数据形式。比如Json形式,方便观察。 我代码中前面还是挺规范,到后面全是@RequestMapping,这不好。 @RequestParam和@RequestBody都是定义网络请求参数名称,前者参数是普通参数(parameter),比如String或Long;后者参数是一个类。 我代码中前面还是挺规范,到后面全都漏了,这不好。 你可能注意到了,我的所有实体的属性都是小写,单词之间由下划线分开。比如department_name。这是因为如果不这么做,Mysql会自动把这些属性转化成表中的Title时,自动变小写,单词用下划线分开。按理说分开就分开,但是我试了一下,会引起一个我搞不懂的bug。于是就向Mysql妥协。 这就是个接口类,由ServiceImpl实现。 @org.springframework.stereotype.Service publicinterfaceService{ publicvoidinsertEmployee(Employeeemployee); publicLongcountEmployee(Employeeemployee); publicvoiddeleteEmployee(Stringid); publicvoidupdateEmployee(Employeeemployee); publicEmployeesearchEmployee(Stringid); publicvoidaddDepartment(Departmentdepartment); publicLongcountDepartment(Departmentdepartment); publicvoiddeleteDepartment(Longdepartment_id); publicDepartmentsearchDepartment(Stringdepartment_id); publicvoidupdateDepartment(Departmentdepartment); publicvoidaddConnection(Connectionconnection); publicLongcountConnection(Connectionconnection); publicvoiddeleteConnection(Longconnection_id); publicvoidupdateConnection(Connectionconnection); publicList publicList publicLongcountEmployeeFromDepartment(Stringdepartment_name); publicvoiddeleteEmployeeFromDepartment(Longemployee_id,Longdepartment_id); publicLongemployeeExistAndIdCorrect(@Param("connection")Connectionconnection); publicLongdepartmentExistAndIdCorrect(@Param("connection")Connectionconnection); } @Service注释是一个指明该类是个独立服务层的注释,它是@Component的一种,能被扫描到。 这里它变成@org.springframework.stereotype.Service这么长,是因为我的类名也是Service,系统为了区分,就写长了。 @org.springframework.stereotype.Service @Transactional publicclassServiceImplimplementsService{ @Autowired privateMappermapper; publicvoidinsertEmployee(Employeeemployee){ mapper.insertEmployee(employee); } publicLongcountEmployee(Employeeemployee){ returnmapper.countEmployee(employee); } publicvoiddeleteEmployee(Stringid){ mapper.deleteEmployee(id); } publicvoidupdateEmployee(Employeeemployee){ mapper.updateEmployee(employee); } publicEmployeesearchEmployee(Stringid){ returnmapper.searchEmployee(id); } publicvoidaddDepartment(Departmentdepartment){ mapper.addDepartment(department); } publicLongcountDepartment(Departmentdepartment){returnmapper.countDepartment(department);} publicvoiddeleteDepartment(Longdepartment_id){ mapper.deleteDepartment(department_id); } publicDepartmentsearchDepartment(Stringdepartment_id){ returnmapper.searchDepartment(department_id); } publicvoidupdateDepartment(Departmentdepartment){ mapper.updateDepartment(department); } publicvoidaddConnection(Connectionconnection){ mapper.addConnection(connection); } publicLongcountConnection(Connectionconnection){returnmapper.countConnection(connection);} publicvoiddeleteConnection(Longconnection_id){ mapper.deleteConnection(connection_id); } publicvoidupdateConnection(Connectionconnection){ mapper.updateConnection(connection); } publicList returnmapper.searchDepartmentFromEmployee(employee_name); } publicList returnmapper.searchEmployeeFromDepartment(department_name); } publicLongcountEmployeeFromDepartment(Stringdepartment_name){ returnmapper.countEmployeeFromDepartment(department_name); } publicvoiddeleteEmployeeFromDepartment(Longemployee_id,Longdepartment_id){ mapper.deleteEmployeeFromDepartment(employee_id,department_id); } publicLongemployeeExistAndIdCorrect(@Param("connection")Connectionconnection){ returnmapper.employeeExistAndIdCorrect(connection); } publicLongdepartmentExistAndIdCorrect(@Param("connection")Connectionconnection){ returnmapper.departmentExistAndIdCorrect(connection); } } @Transactional注释是一个为独立方法或一个类的转换属性的注释。 使用Mysql命令,用于将对象与Mysql里的表打交道。 @org.apache.ibatis.annotations.Mapper publicinterfaceMapper{ @Insert("insert into employee(name,sex,address) values(#{name},#{sex},#{address})") @Options(useGeneratedKeys=true,keyColumn="id")//展示id voidinsertEmployee(Employeeemployee); @Select("select count(*) from employee where name = #{name}") LongcountEmployee(Employeeemployee); @Delete("delete from employee where id = #{id}") voiddeleteEmployee(@Param("id")Stringid); @Update("update employee set name = #{name},sex = #{sex},address = #{address} where id = #{id}") @Options(useGeneratedKeys=true,keyColumn="id") voidupdateEmployee(@Param("employee")Employeeemployee); @Select("select * from employee where id = #{id}") @Options(useGeneratedKeys=true,keyColumn="id") EmployeesearchEmployee(@Param("id")Stringid); @Insert("insert into department(department_name) values(#{department_name})") @Options(useGeneratedKeys=true,keyProperty="department_id") voidaddDepartment(Departmentdepartment); @Select("select count(*) from department where department_name = #{department_name}") LongcountDepartment(Departmentdepartment); @Delete("delete from department where department_id = #{department_id}") voiddeleteDepartment(@Param("department_id")Longdepartment_id); @Select("select * from department where department_id = #{department_id}") @Options(useGeneratedKeys=true,keyProperty="department_id") DepartmentsearchDepartment(@Param("department_id")Stringdepartment_id); @Update("update department set department_id = #{department_id}, department_name = #{department_name} where department_id = #{department_id}") @Options(useGeneratedKeys=true,keyColumn="department_id",keyProperty="department_id") voidupdateDepartment(@Param("department")Departmentdepartment); @Insert("insert into connection(department_id,department_name,employee_id,employee_name)values(#{department_id},#{department_name},#{employee_id},#{employee_name})") @Options(useGeneratedKeys=true,keyColumn="connection_id",keyProperty="connection_id") voidaddConnection(Connectionconnection); @Select("select count(*) from connection where department_name = #{department_name} and employee_name = #{employee_name}") LongcountConnection(Connectionconnection); @Delete("delete from connection where connection_id = #{connection_id}") voiddeleteConnection(Longconnection_id); @Delete("delete from connection where employee_id = #{employeeId} and department_id = #{department_id}") voiddeleteEmployeeFromDepartment(@Param("employeeId")Longemployee_id, @Param("department_id")Longdepartment_id); @Update("update connection set employee_id = #{employee_id}, employee_id = #{employee_id}, department_id = #{department_id} , employee_name = #{employee_name} where connection_id = #{connection_id}") @Options(useGeneratedKeys=true,keyColumn="connection_id",keyProperty="connection_id") voidupdateConnection(Connectionconnection); @Select("select * from connection where employee_name = #{employee_name}") @Options(useGeneratedKeys=true,keyColumn="employee_name",keyProperty="employee_name") List @Select("select * from connection where department_name = #{department_name}") @Options(useGeneratedKeys=true,keyColumn="department_name",keyProperty="department_name") List @Select("select count(*) from connection where department_name = #{department_name}") publicLongcountEmployeeFromDepartment(@Param("department_name")Stringdepartment_name); @Select("select count(*) from employee where name = #{employee_name} and id = #{employee_id}") publicLongemployeeExistAndIdCorrect(@Param("connection")Connectionconnection); @Select("select count(*) from department where department_name = #{department_name} and department_id = #{department_id}") publicLongdepartmentExistAndIdCorrect(@Param("connection")Connectionconnection); } 以上括号内的语句就是Mysql命令,其相关意思网上多的很,我就不解释了。就算是新人也能通过字面意思与上文结合,猜知大概。 其实这里应该与代码测试穿插着用,上文到这不是真正开始制作的顺序。 如果ENtity内写了表名,那么在最开始船舰的数据库下就会自动生成相应的表,以及该表的第一列;若没有,则手动生成。 在每次调用接口测试代码时,它会要求每张表都要有自增主键。于是,我们要右键表名,选择“Design Table”,选择相应主键,勾选自增(Auto-Increment)。 它刷新时间意不要用右键选择刷新,而要用底部的刷新键,或是重启Navicat。 除了上文所提及的各种事项,我再整理一些修理bug的总结。 各种依赖包内的类别引用错了,很多都是同名的,注意选择你想要的。假如你和我一样对理论不熟悉,就要点开(“ctrl" + 左键)这些类一看究竟。有时他甚至不会一下显示这些同名的类,坑的我好惨。 写代码要规范,养成好习惯。有许多现在看来可写可不写的,那是因为我们程序功能简单。以后功能复杂起来,再不写规范,那就是一坨屎,不仅没人想看,自己过几天也不想看。启动类
Druid连接
Entity创建
Employee
数据处理
Service
ServiceImpl
Mapper
Navicat使用
注意事项