前言最近在部署Spring Boot构建的Api因为有多个jar要启动，启动的参数也都不一样 搞的我都蛋疼了，只好研究下使用shell脚本来一键管理了花了两个小时，终于把脚本撸出来了 注意如果在windows下创建shell脚本文件，注意文档格式要转换为unix才能在linux中运行推荐直接在linux中直接使用vi创建shell脚本文件创建脚本在指定路径下运行vi startAll.sh来创建脚本文件参考如下代码：#!/bin/sh ## java安装目录 export JAVA_HOME=/usr/java/jdk1.8 export JRE_HOME=$JAVA_HOME/jre # 端口号 PORTS=(7077 7078) # 模块 MODULES=(api-1 api-2) # 模块名称 MODULE_NAMES=(测试-1 测试-2) # jar包数组 JARS=(xxx1.jar xxx2.jar) # jar包路径 JAR_PATH='/data/test' # 日志路径 LOG_PATH='/data/test/logs' # 启动方法 start() { echo "----------启动服务----------" local MODULE= local MODULE_NAME= local JAR_NAME= local command="$1" local commandOk=0 local count=0 local okCount=0 local port=0 for((i=0;i<${#MODULES[@]};i++)) do MODULE=${MODULES[$i]} MODULE_NAME=${MODULE_NAMES[$i]} JAR_NAME=${JARS[$i]} PORT=${PORTS[$i]} if [ "$command" == "all" ] || [ "$command" == "$MODULE" ];then commandOk=1 count=0 if [ ! -d $LOG_PATH ];then mkdir $LOG_PATH fi PID=`ps -ef |grep $(echo $JAR_NAME | awk -F/ '{print $NF}') | grep -v grep | awk '{print $2}'` if [ -n "$PID" ];then echo "$MODULE---$MODULE_NAME:已经运行,PID=$PID" else #这行命令根据自己的需要更改 cd $JAR_PATH && nohup $JRE_HOME/bin/java -Xms256m -Xmx512m -jar $JAR_NAME --spring.config.location=config/application.yml --server.port=$PORT >> $LOG_PATH/$MODULE.log 2>&1 & PID=`ps -ef |grep $(echo $JAR_NAME | awk -F/ '{print $NF}') | grep -v grep | awk '{print $2}'` while [ -z "$PID" ] do if (($count == 10));then echo "$MODULE---$MODULE_NAME:$(expr $count \* 10)秒内未启动,请检查!" break fi count=$(($count+1)) echo "$MODULE_NAME启动中..." sleep 10s PID=`ps -ef |grep $(echo $JAR_NAME | awk -F/ '{print $NF}') | grep -v grep | awk '{print $2}'` done okCount=$(($okCount+1)) echo "$MODULE---$MODULE_NAME:已经启动成功,PID=$PID" fi fi done if(($commandOk == 0));then echo "第二个参数输入错误" else echo "----本次共启动:$okCount个服务----" fi } # 停止方法 stop() { echo "----------停止服务----------" local MODULE= local MODULE_NAME= local JAR_NAME= local command="$1" local commandOk=0 local okCount=0 for((i=0;i<${#MODULES[@]};i++)) do MODULE=${MODULES[$i]} MODULE_NAME=${MODULE_NAMES[$i]} JAR_NAME=${JARS[$i]} if [ "$command" = "all" ] || [ "$command" = "$MODULE" ];then commandOk=1 PID=`ps -ef |grep $(echo $JAR_NAME | awk -F/ '{print $NF}') | grep -v grep | awk '{print $2}'` if [ -n "$PID" ];then echo "$MODULE---$MODULE_NAME:准备结束,PID=$PID" kill -9 $PID PID=`ps -ef |grep $(echo $JAR_NAME | awk -F/ '{print $NF}') | grep -v grep | awk '{print $2}'` while [ -n "$PID" ] do sleep 3s PID=`ps -ef |grep $(echo $JAR_NAME | awk -F/ '{print $NF}') | grep -v grep | awk '{print $2}'` done echo "$MODULE---$MODULE_NAME:成功结束" okCount=$(($okCount+1)) else echo "$MODULE---$MODULE_NAME:未运行" fi fi done if (($commandOk == 0));then echo "第二个参数输入错误" else echo "----本次共停止:$okCount个服务----" fi } # 检查运行状态 status(){ echo "------检查服务运行状态------" local MODULE= local MODULE_NAME= local JAR_NAME= local command="$1" local commandOk=0 local okCount=0 for((i=0;i<${#MODULES[@]};i++)) do MODULE=${MODULES[$i]} MODULE_NAME=${MODULE_NAMES[$i]} JAR_NAME=${JARS[$i]} if [ "$command" = "all" ] || [ "$command" = "$MODULE" ];then commandOk=1 PID=`ps -ef |grep $(echo $JAR_NAME | awk -F/ '{print $NF}') | grep -v grep | awk '{print $2}'` if [ -n "$PID" ];then echo "$MODULE---$MODULE_NAME:正在运行,PID=$PID" okCount=$(($okCount+1)) else echo "$MODULE---$MODULE_NAME:未运行" fi fi done if (($commandOk == 0));then echo "第二个参数输入错误" else echo "------正在运行:$okCount个服务------" fi } # 根据输入参数，选择执行对应方法，不输入则执行使用说明 case "$1" in start) start "$2" ;; stop) stop "$2" ;; status) status "$2" ;; restart) stop "$2" sleep 3s start "$2" ;; *) echo "sh startAll.sh [start|stop|restart|status] [all|模块名]" exit 1 ;; esac exit 0输入脚本文件内容后，按esc退出编辑模式，输入:wq保存退出，脚本就做好了给脚本可执行权限chmod a+x startAll.sh给了权限就可以运行脚本来批量的管理api服务了# 启动所有jar包 ./startAll.sh start all # 停止所有jar包 ./startAll.sh stop all # 重启所有jar包 ./startAll.sh restart all # 查看所有服务状态 ./startAll.sh status all # 如果需要操作单个jar包，只需要将all替换为需要操作的模块名而已到此，批量管理api就完成了，可是既然都批量管理了，为何不一步到位开机自动启动呢？设置脚本的开启自动启动批量管理的脚本已经做好了，接下来可以制作一个系统服务，用来通过systemctl直接操作脚本还可以直接添加开机启动，该操作只在CentOS7下测试通过，CentOS6可能有点不太一样经过查询相关资料，CentOS7的systemctl脚本文件储存在/usr/lib/systemd/目录下有系统（system）和用户（user）之分，其中系统服务为开机后不需要用户登录就可以运行的服务所以我们将脚本存放在系统目录下，即 /usr/lib/systemd/system创建systemctl脚本使用vim创建脚本，文件后缀为.servicevi /usr/lib/systemd/system/myapis.service参考以下代码来编写内容[Unit] #服务描述 Description=fanfan's api service #在以下服务之后启动 After=syslog.target network.target [Service] #服务运行模式：后台模式 Type=forking #启动服务时执行的命令 ExecStart=/data/api/domain/startAll.sh start all #停止服务时执行的命令 ExecStop=/data/api/domain/startAll.sh stop all #重启服务时执行的命令 ExecReload=/data/api/domain/startAll.sh restart all #表示给服务分配独立的临时空间 PrivateTmp=True #服务运行的用户和用户组 User=root Group=root #非正常退出时，自动重启，用作守护进程 Restart=on-failure [Install] #依赖配置 WantedBy=multi-user.target编写好脚本文件后，按esc退出编辑模式，输入:wq保存退出，系统服务就做好了添加可执行权限chmod a+x /usr/lib/systemd/system/myapis.service重载系统服务以使脚本生效systemctl daemon-reload先查看状态，显示load即脚本正常载入systemctl status myapis可使用一下命令来进行批量操作# 启动服务 systemctl start myapis # 关闭服务 systemctl stop myapis # 重启服务 systemctl restart myapis # 查看服务状态 systemctl status myapis将服务添加开机自动启动# 添加开机自启 systemctl enable tomcat.service # 关闭开机自启 systemctl disable tomcat.service若执行命令提示 Access denied解决方案有两种:1 systemctl daemon-reexec2 setenforce 0这样就可以直接使用systemctl命令来批量管理api服务了，并且实现了开机自动启动 shell脚本参考了zns：https://www.cnblogs.com/zengnansheng/p/11380009.html
念念不忘，必有回响，心仪的域名终于注册成功辣~
前些天看中了一个域名，是由我的名字简写的域名 fanhy.com，正处于赎回期，原注册者也不打算赎回了 于是就写了个监控域名注册的Api来监控该域名，等域名可以注册了就给我发送邮件通知 传送门：自己动手造了个监控域名可否注册的Api → CheckDomain经过半个多月的等待，终于在今日凌晨，该域名终于被注册局删除 由于我早就推算好时间，今天睡觉的时候就给邮件通知加上了铃声通知，于是凌晨3点左右就收到了域名可以注册的邮件梦中惊醒，打开早就准备好的注册页面，下单，付款一气呵成，域名终于到手！ 开心~~~ 不过注册后了才发现是一时冲动，手上已经有两个域名了，拿这个域名干什么呢？ 不管这么多了，先拿去备案再说吧~网站备案期间需要暂时关闭留言功能...
博客的友情链接从首页移至内页啦
由于友情链接日益增多已经影响阅读，风格也过于单调 所以就抽了个时间把友链从首页移动到内页这样就能给友链加特效啦 友链的风格设计 参考了 基本照抄了：https://logi.ml/添加了头像的展示，效果如下：没有头像的小伙伴们还不快给我留言添加头像~~~
昨天改完CheckDomain的bug后，打包jar准备部署到服务器上，本地测试都正常，但是扔到服务器上却发不了邮件了... 经过多次测试发现，freemarker在jar包中无法使用类加载器获取resourse目录下的templates文件 出现的问题代码如下：（本地测试正常，打包jar后无法获取模板）/** * 邮件模板静态化 * * @param mailTemplateModel 模板数据模型 * @return 加上数据后的静态化模板 */ @Override public String getMailHtml(MailTemplateModel mailTemplateModel) { try { //创建配置类 Configuration configuration = new Configuration(Configuration.getVersion()); //设置模板路径 String classpath = this.getClass().getResource("/").getPath(); configuration.setDirectoryForTemplateLoading(new File(classpath + "/templates")); //设置字符集 configuration.setDefaultEncoding("utf-8"); //加载模板 Template template = configuration.getTemplate("mailTemplate.ftl"); //模板静态化并返回 return FreeMarkerTemplateUtils.processTemplateIntoString(template, mailTemplateModel); } catch (Exception e) { e.printStackTrace(); log.error("模板静态化异常", e); return null; } }修改后的代码：（打包jar后正常获取模板）@Autowired FreeMarkerConfigurer freeMarkerConfigurer; /** * 邮件模板静态化 * * @param mailTemplateModel 模板数据模型 * @return 加上数据后的静态化模板 */ @Override public String getMailHtml(MailTemplateModel mailTemplateModel) { try { //创建配置类 Configuration configuration = new Configuration(Configuration.getVersion()); //设置模板路径 //String classpath = this.getClass().getResource("/").getPath(); //configuration.setDirectoryForTemplateLoading(new File(classpath + "/templates")); //设置字符集 configuration.setDefaultEncoding("utf-8"); //加载模板 //Template template = configuration.getTemplate("mailTemplate.ftl"); Template template = freeMarkerConfigurer.getConfiguration().getTemplate("mailTemplate.ftl"); //模板静态化并返回 return FreeMarkerTemplateUtils.processTemplateIntoString(template, mailTemplateModel); } catch (Exception e) { e.printStackTrace(); log.error("模板静态化异常", e); return null; } }PS: .ftl模板文件放在 templates 目录下总结：freemarker无法使用类加载器获取jar包中的resourse目录下的templates文件解决办法：注入FreeMarkerConfigurer配置类，因为freemarker模板的默认目录就在resourse下的templates目录下，使用freeMarkerConfigurer.getConfiguration().getTemplate("mailTemplate.ftl")可直接获取到对应的模板文件
前两天我看中了一个域名处于即将删除的状态 ，想着如果能监控域名能注册了给我发邮件就好了，于是就花了一晚上撸出了 CheckDomain 来帮我监控这个域名 。CheckDomain 是一个用来检查域名是否能注册的 Api，基于 Spring Boot 构建，使用了阿里云域名查询 Api；可实现对域名状态查询，可注册监控等，并在可以注册的时候发送邮件通知。也算是自己动手做的第一个完整的项目吧 既然有了兴趣点，所以就准备把所学的知识给串一下，能用上的都用上，也算是学以致用吧~ Github：https://github.com/noisky/CheckDomainDemo：https://whois.ffis.me接口调用：1、查看域名状态：请求方式：GET请求请求地址：/domain/{name}参数说明：{name}： 查询的域名2、查看域名状态并在可注册时发送邮件通知：1）请求方式：GET请求请求地址：/domain/{name}/{email}/{querykey}参数说明：{name}： 查询的域名{email}： 接受通知的邮箱地址querykey： 查询密码，在 yml 配置文件中设置2）请求方式：POST请求请求地址：/domain/{name}参数说明：{name}： 查询的域名POST 提交的表单：email： 接受通知的邮箱地址querykey： 查询密码，在 yml 配置文件中设置可以使用第三方监控（如阿里云监控）来请求 Api 来达到监控域名注册状态现在已经实现了基本功能：检查域名是否能注册监控域名状态，如果可以注册，发送邮件通知使用 Freemarker 实现邮件模板的静态化使用 Logback 记录系统运行日志实现域名的 whois 信息查询（参考了 tammypi 的 whoisutil 工具类）已完成编写前端页面，现在可以直接在页面上查询 whois 信息了TODO：限制接口的调用频率统计并记录接口的调用次数，调用耗时等信息...使用了万网域名查询接口（已经自己实现直接向whois服务器查询信息）：接口地址：http://panda.www.net.cn/cgi-bin/check.cgi接口采用HTTP，POST，GET协议参数名称：area_domain 值为标准域名，例：xxx.com返回 XML 结果说明：返回字段说明200表示接口返回成功xxx.com表示当前检查的域名210Domain name is available 表示域名可以注册211Domain name is not available 表示域名已经注册212Domain name is invalid 表示域名参数传输错误213Time out 查询超时注意：这个接口返回的数据虽然看起来是xml格式，但是响应头却是 text/plain 格式的，搞的我踩了好多深坑...详见：记一次解决RestTemplate无法解析api返回的xml数据问题
最近看上了个本命域名，因为过期即将被注册局删除，于是就自己写了个api接口用来监控这个域名，等可以注册了就直接发邮件通知我 其中用到了阿里云域名的一个api，准备直接使用RestTemplate去请求这个接口，并将结果自动封装为Map集合方便进行进一步处理。等到框架搭好，代码写完，以为万事大吉的时候，程序却给我来了个惊喜： org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map] and content type [text/plain;charset=GBK]很明显，上面提示接口返回的是text/plain文本类型数据，RestTemplate解析数据的时候没有找到合适的解析器，就抛了异常。WTF？ 我看阿里云的接口明明返回的是xml的数据啊......仔细再定睛一看，类型还真的是文本类型...我嘞个去，还有这种操作？ 这可怎么办？当时我没想太多，就直接将返回值保存为String类型，然后使用jackson直接解析成对象了，这样也能进行数据处理了;不过问题虽然暂时解决了，可终究不是好办法，RestTemplate本身就内置了jackson解析器，所以说肯定能直接让RestTemplate解析这个数据的;本着极客精神，今天有空就看了下源码：根据程序提示的异常信息：org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map] and content type [text/plain;charset=GBK] at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:123) at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1001) at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:984)先进入HttpMessageConverterExtractor.java中，于是看到如下逻辑HttpMessageConverter<?> messageConverter = (HttpMessageConverter)var4.next(); if (messageConverter instanceof GenericHttpMessageConverter) { GenericHttpMessageConverter<?> genericMessageConverter = (GenericHttpMessageConverter)messageConverter; if (genericMessageConverter.canRead(this.responseType, (Class)null, contentType)) { if (this.logger.isDebugEnabled()) { ResolvableType resolvableType = ResolvableType.forType(this.responseType); this.logger.debug("Reading to [" + resolvableType + "]"); } return genericMessageConverter.read(this.responseType, (Class)null, responseWrapper); } } if (this.responseClass != null && messageConverter.canRead(this.responseClass, contentType)) { if (this.logger.isDebugEnabled()) { String className = this.responseClass.getName(); this.logger.debug("Reading to [" + className + "] as \"" + contentType + "\""); } return messageConverter.read(this.responseClass, responseWrapper); }可以看到messageConverter.canRead(this.responseClass, contentType)方法是根据contentType来判断这个MessageConverter能不能处理响应的内容，由于我需要处理的类型为xml，所以就进入MappingJackson2XmlHttpMessageConverter类，并终于在它的爸爸类AbstractJackson2HttpMessageConverter的爷爷类AbstractGenericHttpMessageConverter的祖宗类AbstractHttpMessageConverter中的canRead方法中找到了对应的处理逻辑：protected boolean canRead(@Nullable MediaType mediaType) { if (mediaType == null) { return true; } else { Iterator var2 = this.getSupportedMediaTypes().iterator(); MediaType supportedMediaType; do { if (!var2.hasNext()) { return false; } supportedMediaType = (MediaType)var2.next(); } while(!supportedMediaType.includes(mediaType)); return true; } }这里可以看到canRead方法就是根据内置的mediaType列表来判断是否能够解析传入的contentType类型，所以解决方法就是将text/plain文本类数据加入到xml解析器的mediaType列表中，让xml解析器默认支持解析text/plain类型，可以直接在创建RestTemplate的时候实现，具体代码如下：@Bean public RestTemplate restTemplate() { //创建RestTemplate对象，并且使用okHttp客户端 RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory()); //先获取到converter列表 List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters(); for(HttpMessageConverter<?> converter : converters){ //让jsonConverter支持对text/plain的解析 if(converter instanceof MappingJackson2XmlHttpMessageConverter){ try{ //先将原先支持的MediaType列表拷出 List<MediaType> mediaTypeList = new ArrayList<>(converter.getSupportedMediaTypes()); //加入对text/plain的支持 mediaTypeList.add(MediaType.TEXT_PLAIN); //将已经加入了text/plain的MediaType支持列表设置为其支持的媒体类型列表 ((MappingJackson2XmlHttpMessageConverter) converter).setSupportedMediaTypes(mediaTypeList); }catch(Exception e){ e.printStackTrace(); } } } return restTemplate; }至此，我的RestTemplate就能处理返回的是xml数据，不过类型为text/plain的数据了
1 Docker简介Docker 是一个开源的应用容器引擎，基于 Go 语言开发。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中，然后发布到任何流行的 Linux 机器上，也可以实现虚拟化。容器是完全使用沙箱机制，相互之间不会有任何接口（类似 iPhone 的 app）,更重要的是容器性能开销极低。Docker应用场景Web应用的自动化打包和发布自动化测试和持续集成、发布在服务型环境中部署和调整数据库或其他的后台应用简单的说：使用Docker可以实现开发人员的开发环境、测试人员的测试环境、运维人员的生产环境的一致性。Docker借鉴了标准集装箱的概念。标准集装箱将货物运往世界各地，Docker将这个模型运用到自己的设计中，唯一不同的是：集装箱运输货物，而Docker运输软件。Docker组成部分名称说明Docker 镜像(Images)Docker 镜像是用于创建 Docker 容器的模板。 镜像是基于联合文件系统的一种层式结构，由一系列指令一步一步构建出来。Docker 容器(Container)容器是独立运行的一个或一组应用。镜像相当于类，容器相当于类的实例Docker 客户端(Client)Docker 客户端通过命令行或者其他工具使用 Docker API 与 Docker 的守护进程通信。Docker 主机(Host)一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。Docker守护进程是Docker服务器端进程，负责支撑Docker 容器的运行以及镜像的管理。Docker 仓库DockerHub(Registry)Docker 仓库用来保存镜像，可以理解为代码控制中的代码仓库。 Docker Hub提供了庞大的镜像集合供使用。用户也可以将自己本地的镜像推送到Docker仓库供其他人下载。2 Docker安装与启动2.1 Dockek安装Docker可以运行在MAC、Windows、CentOS、DEBIAN、UBUNTU等操作系统上，提供社区版和企业版下面在centos7中安装Docker# 1、yum 包更新到最新 sudo yum update # 2、作用：安装需要的软件包， yum-util 提供yum-config-manager功能，另外两个是devicemapper驱动依 赖的 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 # 3、 设置yum源 # 3.1、方案一：使用ustc的 sudo yum-config-manager --add-repo http://mirrors.ustc.edu.cn/dockerce/ linux/centos/docker-ce.repo # 3.2、方案二：使用阿里云 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/dockerce/ linux/centos/docker-ce.repo # 4、 安装docker；出现输入的界面都按 y sudo yum install -y docker-ce # 5、 查看docker版本 docker -v2.2 设置USTC镜像ustc是老牌的linux镜像服务提供者了，还在遥远的ubuntu 5.04版本的时候就在用。ustc的docker镜像加速器速度很快。ustc docker mirror的优势之一就是不需要注册，是真正的公共服务https://lug.ustc.edu.cn/wiki/mirrors/help/docker1、 编辑文件/etc/docker/daemon.json# 执行如下命令： mkdir /etc/docker vi /etc/docker/daemon.json2、在文件中加入下面内容{ "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"] }2.3 Docker启动与停止命令# 启动docker服务： systemctl start docker # 停止docker服务： systemctl stop docker # 重启docker服务： systemctl restart docker # 查看docker服务状态： systemctl status docker # 设置开机启动docker服务： systemctl enable docker3 Docker常用命令3.1. 镜像相关命令镜像：Docker镜像是由文件系统叠加而成（是一种文件的存储形式）；是docker中的核心概念，可以认为镜像就是对某些运行环境或者软件打的包，用户可以从docker仓库中下载基础镜像到本地，比如开发人员可以从docke仓库拉取（下载）一个只包含centos7系统的基础镜像，然后在这个镜像中安装jdk、mysql、Tomcat和自己开发的应用，最后将这些环境打成一个新的镜像。开发人员将这个新的镜像提交给测试人员进行测试，测试人员只需要在测试环境下运行这个镜像就可以了，这样就可以保证开发人员的环境和测试人员的环境完全一致。Docker提供的关于镜像的操作有：查看镜像搜索镜像拉取镜像删除镜像3.1.1 查看镜像# 查看镜像可以使用如下命令： docker images1574334973611.pngREPOSITORY：镜像名称TAG：镜像标签IMAGE ID：镜像IDCREATED：镜像的创建日期3.1.2 搜索镜像# 如果你需要从网络中查找需要的镜像，可以通过以下命令搜索 docker search 镜像名称NAME：镜像名称DESCRIPTION：镜像描述STARS：用户评价，反应一个镜像的受欢迎程度OFFICIAL：是否官方AUTOMATED：自动构建，表示该镜像由Docker Hub自动构建流程创建的3.1.3 拉取镜像# 拉取镜像就是从Docker仓库下载镜像到本地，镜像名称格式为 名称:版本号，如果版本号不指定则是最新的版本 命令如下： docker pull 镜像名称 # 如拉取tomcat； docker pull tomcat3.1.4 删除镜像# 可以按照镜像id删除镜像，命令如下： docker rmi 镜像id # 删除所有镜像 docker rmi `docker images -q`3.2 容器相关命令容器，也是docker中的核心概念，容器是由镜像运行产生的运行实例。镜像和容器的关系，就如同Java语言中类和对象的关系。Docker提供的关于容器的操作有：查看容器创建容器启动容器停止容器文件拷贝目录挂载查看容器ip地址删除容器3.2.1 查看容器查看正在运行的容器使用命令：docker ps查看所有容器使用命令：docker ps -a3.2.2 创建并启动容器可以基于已有的镜像来创建和启动容器，创建与启动容器使用命令：docker run参数说明：-i：表示运行容器-t：表示容器启动后会进入其命令行。加入这两个参数后，容器创建就能登录进去。即分配一个伪终端。--name :为创建的容器命名。-v：表示目录映射关系（前者是宿主机目录，后者是映射到宿主机上的目录），可以使用多个－v做多个目录或文件映射。注意：最好做目录映射，在宿主机上做修改，然后共享到容器上。-d：在run后面加上-d参数,则会创建一个守护式容器在后台运行（这样创建容器后不会自动登录容器，如果只加-i -t两个参数，创建后就会自动进去容器）。-p：表示端口映射，前者是宿主机端口，后者是容器内的映射端口。可以使用多个-p做多个端口映射1）交互式容器以交互式方式创建并启动容器，启动完成后，直接进入当前容器。使用exit命令退出容器。需要注意的是以此种方式启动容器，如果退出容器，则容器会进入停止状态。# 先拉取一个镜像 docker pull centos:7 #创建并启动名称为 mycentos7 的交互式容器；下面指令中的镜像名称 centos:7 也可以使用镜像id docker run -it --name=mycentos7 centos:7 /bin/bash2）守护式容器创建一个守护式容器；如果对于一个需要长期运行的容器来说，我们可以创建一个守护式容器。命令如下（容器名称不能重复）：#创建并启动守护式容器 docker run -di --name=mycentos2 centos:7 #登录进入容器命令为：docker exec -it container_name (或者 container_id) /bin/bash（exit退出时，容器不会停止） docker exec -it mycentos2 /bin/bash3.2.3 停止并启动容器# 停止正在运行的容器：docker stop 容器名称或者ID docker stop mycentos2 # 启动已运行过的容器：docker start 容器名称或者ID docker start mycentos2 # 停止所有容器 docker stop `docker ps -a -q`3.2.4 文件拷贝将linux宿主机中的文件拷贝到容器内可以使用命令：# docker cp 需要拷贝的文件或目录 容器名称:容器目录 # 创建一个文件abc.txt touch abc.txt # 复制abc.txt到mycentos2的容器的 / 目录下 docker cp abc.txt mycentos2:/ # 进入mycentos2容器 docker exec -it mycentos2 /bin/bash # 查看容器 / 目录下文件 ll将文件从容器内拷贝出来到linux宿主机使用命令：# docker cp 容器名称:容器目录 需要拷贝的文件或目录 #进入容器后创建文件cba.txt touch cba.txt # 退出容器 exit # 在Linux宿主机器执行复制；将容器mycentos2的/cba.txt文件复制到 宿主机器的/root目录下 docker cp mycentos2:/cba.txt /root 注意：容器在停止状态下也可以完成文件的拷贝PS：如果提示ll/vim等命令不存在，则是因为没有容器没有安装对应的命令导致解决办法：# 进入要操作的容器，mysql是容器名称，换成你自己的 docker exec -it mysql bash # 升级apt-get apt-get update # 安装需要的命令即可 apt-get install vim -y 3.2.5 目录挂载可以在创建容器的时候，将宿主机的目录与容器内的目录进行映射，这样我们就可以通过修改宿主机某个目录的文件从而去影响容器。创建容器时添加-v参数，后边为宿主机目录:容器目录例如： docker run -di -v /usr/local/test:/usr/local/test --name=mycentos3 centos:7# 创建linux宿主机器要挂载的目录 mkdir /usr/local/test # 创建并启动容器mycentos3,并挂载linux中的/usr/local/test目录到容器的/usr/local/test； # 也就是在linux中的/usr/local/test中操作相当于对容器相应目录操作 docker run -di -v /usr/local/test:/usr/local/test --name=mycentos3 centos:7 # 在linux下创建文件 touch /usr/local/test/def.txt # 进入容器 docker exec -it mycentos3 /bin/bash # 在容器中查看目录中是否有对应文件def.txt ll /usr/local/test # 注意：如果你共享的是多级的目录，可能会出现权限不足的提示。 这是因为CentOS7中的安全模块selinux把权限禁掉了，需要添加参数 --privileged=true 来解决挂载的目录没有权限的问题。3.2.6 查看容器ip可以通过以下命令查看容器运行的各种数据 docker inspect 容器名称（容器ID）# 在linux宿主机下查看 nginx 的ip docker inspect mynginx# 可以使用grep过滤出需要的信息 docker inspect mynginx | grep IPAddress容器之间在一个局域网内，linux宿主机器可以与容器进行通信；但是外部的物理机笔记本是不能与容器直接通信的，如果需要则需要通过宿主机器端口的代理。3.2.7 删除容器删除指定的容器：docker rm 容器名称（容器ID）# 删除容器 docker rm mycentos7 # 删除所有容器 docker rm `docker ps -a -q`如果容器是运行状态则删除失败，需要停止容器才能删除4 Docker应用部署这里仅测试Mysql、Tomcat、nginx、redis等应用4.1 部署Mysql4.1.1 拉取镜像# 拉取MySQL 5.7镜像 docker pull centos/mysql-57-centos74.1.2 创建容器# 创建mysql5.7容器 docker run -di --name=mysql5.7 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root centos/mysql-57-centos7 参数说明： -p 代表端口映射，格式为 宿主机映射端口:容器运行端口 -e 代表添加环境变量 MYSQL_ROOT_PASSWORD 是root用户的远程登陆密码（如果是在容器中使用root登录的话， 那么其密码为空）4.1.3 进入容器操作Mysql# 进入mysql5.7容器中 docker exec -it mysql5.7 /bin/bash # 登录容器里面的mysql，本地登录密码为空 mysql -u root -p # 查看数据库 show databases;4.1.4 远程登录MySql# 查看ip；如果以后要内部连接该mysql，如其他容器中要连接mysql容器的mysql的时候，可以使用如下命令查看Ip docker inspect mysql5.7 | grep IPAddress使用Navicat等客户端在windows中使用ip地址进行远程登录在docker容器中的mysql4.2 部署Tomcat4.2.1 拉取镜像# 拉取tomcat镜像 docker pull tomcat4.2.2 创建容器# 创建tomcat容器;并挂载了webapps目录 docker run -di --name=mytomcat -p 9000:8080 -v /usr/local/tomcat/webapps:/usr/local/tomcat/webapps tomcat # 如果出现 WARNING: IPv4 forwarding is disabled. Networking will not work. #执行如下操作 # 1、编辑 sysctl.conf vi /etc/sysctl.conf # 2、在上述打开的文件中后面添加 net.ipv4.ip_forward=1 # 3、重启network systemctl restart network测试访问宿主机的端口号为9000的 tomcat。地址：http://宿主机ip:9000，也可往/user/local/tomcat/webapps下部署应用，然后再访问。4.3 部署Nginx4.3.1 拉取镜像# 拉取nginx镜像 docker pull nginx4.3.2 创建容器# 创建nginx容器 docker run -di --name=mynginx -p 80:80 nginx启动后访问：http://宿主机IP/4.4 部署Redis4.4.1 拉取镜像# 拉取redis镜像 docker pull redis4.4.2 创建容器# 创建redis容器 docker run -di --name=myredis -p 6379:6379 redis4.4.3 操作redis容器#进入redis容器 docker exec -it myredis /bin/bash #进入redis安装目录 cd /usr/local/bin #连接redis ./redis-cli4.4.4 远程连接redis可以使用redis图形界面客户端工具连接redis，端口也是63794.5 给容器固定ip地址刚刚创建的docker容器 ，默认使用的是bridge网络模式 ，ip地址都是自动分配的，这样就会出现问题，每次重启容器后ip会变化，有的时候需要容器固定ip，但默认网络模式又不支持自定义ip ， 这里我们需要自定义一个birdge网络。 步骤：1、先看下已经配置的网络# 查看网络 docker network ls2、 创建自定义网络# 这里网络名我使用mynet docker network create --subnet=172.20.0.0/16 mynet3、 创建容器并固定ipdocker run -di --name=nginx2 --net mynet --ip 172.20.0.88 nginx:latest /bin/bash这里创建一个nginx容器，使用mynet网络，ip为：172.20.0.884、其他命令docker network ls #查看网络 docker network rm mynet #删除网络 docker inspect nginx #查看容器信息 docker exec -it nginx bash #进入容器5 Docker Compose5.1. Compose简介5.1.1 概念Compose项目是Docker官方的开源项目，负责实现对Docker容器集群的快速编排。它是一个定义和运行多容器的docker应用工具。使用compose，你能通过YAML文件配置你自己的服务，然后通过一个命令，你能使用配置文件创建和运行所有的服务。5.1.2 组成Docker-Compose将所管理的容器分为三层，分别是工程（project），服务（service）以及容器（container）。Docker-Compose运行目录下的所有文件（docker-compose.yml，extends文件或环境变量文件等）组成一个工程，若无特殊指定工程名即为当前目录名。一个工程当中可包含多个服务，每个服务中定义了容器运行的镜像，参数，依赖。一个服务当中可包括多个容器实例。服务（service）：一个应用的容器，实际上可以包括若干运行相同镜像的容器实例。每个服务都有自己的名字、使用的镜像、挂载的数据卷、所属的网络、依赖哪些其他服务等等，即以容器为粒度，用户需要Compose所完成的任务。项目（project）：由一组关联的应用容器组成的一个完成业务单元，在docker-compose.yml中定义。即是Compose的一个配置文件可以解析为一个项目，Compose通过分析指定配置文件，得出配置文件所需完成的所有容器管理与部署操作。Docker-Compose的工程配置文件默认为docker-compose.yml，可通过环境变量COMPOSE_FILE或-f参数自定义配置文件，其定义了多个有依赖关系的服务及每个服务运行的容器。使用一个Dockerfile模板文件，可以让用户很方便的定义一个单独的应用容器。在工作中，经常会碰到需要多个容器相互配合来完成某项任务的情况。例如：要部署一个Web项目，除了Web服务容器，往往还需要再加上后端的数据库服务容器，甚至还包括负载均衡容器等5.2 安装与卸载Compose目前已经完全支持Linux、Mac OS和Windows，在我们安装Compose之前，需要先安装Docker。下面以编译好的二进制包方式安装在Linux系统中。5.2.1 安装# 最新发行的版本地址：https://github.com/docker/compose/releases，要安装其他版本的 Compose，请替换 1.24.0 curl -L "https://github.com/docker/compose/releases/download/1.24.0/dockercompose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # 设置文件可执行权限 chmod +x /usr/local/bin/docker-compose # 查看版本信息 docker-compose -version5.2.2 卸载# 二进制包方式安装的，删除二进制文件即可 rm /usr/local/bin/docker-compose5.3 Compose常用命令参考使用Compose前，可以通过执行docker-compose --help|-h 来查看Compose基本命令用法。也可以通过执行docker-compose [COMMAND] --help 或者docker-compose --help [COMMAND] 来查看某个具体的使用格式。可以知道Compose命令的基本的使用格式为：docker-compose [-f 参数...] [options] 1 [COMMAND] [ARGS...]命令选项如下：-f，–file FILE指定使用的Compose模板文件，默认为docker-compose.yml，可以多次指定。 -p，–project-name NAME指定项目名称，默认将使用所在目录名称作为项目名。 -x-network-driver 使用Docker的可拔插网络后端特性（需要Docker 1.9 及以后版本） -x-network-driver DRIVER指定网络后端的驱动，默认为bridge（需要Docker 1.9 及以后版本） -verbose输出更多调试信息 -v，–version打印版本并退出Docker Compose常用命令列表如下：命令说明build构建项目中的服务容器help获得一个命令的帮助kill通过发送SIGKILL信号来强制停止服务容器config验证和查看compose文件配置create为服务创建容器。只是单纯的create，还需要使用start启动composedown停止并删除容器，网络，镜像和数据卷exec在运行的容器中执行一个命令logs查看服务容器的输出pause暂停一个服务容器port打印某个容器端口所映射的公共端口ps列出项目中目前的所有容器pull拉取服务依赖的镜像push推送服务镜像restart重启项目中的服务rm删除所有（停止状态的）服务容器run在指定服务上执行一个命令scale设置指定服务运行的容器个数start启动已经存在的服务容器stop停止已经处于运行状态的容器，但不删除它top显示运行的进程unpause恢复处于暂停状态中的服务up自动完成包括构建镜像、创建服务、启动服务并关闭关联服务相关容器的一些列操作version打印版本信息5.4 Compose模版文件模板文件是使用Compose的核心，涉及的指令关键字也比较多，大部分指令与docker run 相关参数的含义都是类似的。默认的模板文件名称为docker-compose.yml，格式为YAML格式。比如一个Compose模板文件：version: "2" services: web: images: nginx ports: - "8080:80" volumes: - /usr/local/abc:/usr/local/cba #volumes: #networks:Docker Compose的模板文件主要分为3个区域，如下：services服务，在它下面可以定义应用需要的一些服务，每个服务都有自己的名字、使用的镜像、挂载的数据卷、所属的网络、依赖哪些其他服务等等。volumes数据卷，在它下面可以定义的数据卷（名字等等），然后挂载到不同的服务下去使用。networks应用的网络，在它下面可以定义应用的名字、使用的网络类型等等。Docker Compose常用模板文件主要命令：指令功能build指定服务镜像Dockerfile所在路径cap_add，cap_drop指定容器的内核能力（capacity）分配command覆盖容器启动后默认执行的命令cgroup_parent指定父cgroup组，意味着将基础该组的资源限制container_name指定容器名称。默认将会使用项目名称服务名称序号这样的格式devices指定设置映射关系dns自定义DNS服务器。可以是一个值，也可以是一个列表dns_search配置DNS搜索域。可以是一个值，也可以是一个列表dockerfile指定额外编译镜像的Dockerfile文件，可以通过该指令来指定env_file从文件中获取环境变量，可以为单独的文件路径或列表environment设置环境变量，可以使用数组或字典两种格式expose暴露端口external_links链接到docker-compose.yml外部的容器，甚至可以是非Compose管理的外部容器extra_hosts指定额外的host名称映射信息images指定为镜像名称或镜像ID。如果镜像在本地不存在，Compose将会尝试拉取这个镜像labels指定服务镜像Dockerfile所在路径links链接到其他服务中的容器log_driver指定日志驱动类型，类似于Docker中的–log-driver参数。目前支持三种日志驱动类型：log_driver:“json-file”、log_driver:“syslog”、log_driver:“none”log_opt日志驱动的相关参数net设置网络模式。参数类似于docker clinet的–net参数一样pid跟主机系统共享进程命名空间。打开该选项的容器之间，以及容器和宿主机系统之间可以通过进程ID来相互访问和操作ports暴露端口信息security_opt指定容器模板标签（label）机制的默认属性（如用户、角色、类型、级别等）ulimits指定容器的ulimits限制值volums数据卷所挂载路径设置。可以设置宿主机路径（HOST:CONTAINER）或加上访问模式（HOST:CONTAINER:ro）5.5 Compose应用编写compose模版文件，实现同时启动tomcat、mysql和redis容器。5.5.1 编写模版文件# 创建文件夹 mkdir -p /usr/local/mycompose # 进入文件夹 cd /usr/local/mycompose # 创建 docker-compose.yml文件；内容如下 vi docker-compose.ymldocker-compose.yml文件内容如下：version: '3' services: redis1: image: redis ports: - "6379:6379" container_name: "redis1" networks: - dev mysql1: image: centos/mysql-57-centos7 environment: MYSQL_ROOT_PASSWORD: "root" ports: - "3306:3306" container_name: "mysql1" networks: - dev web1: image: tomcat ports: - "9090:8080" container_name: "web1" networks: - dev - pro networks: dev: driver: bridge pro: driver: bridge上面配置声明了3个服务；分别是：redis1、mysql1、web1；并且对3个服务都指定了对应的docker 镜像和端口；声明了两个自定义网络：dev和pro，类型是 bridge 桥接，相同网络容器之间可以通信，不同网络无法通信5.5.2 启动#启动前最好把docker重启，不然原来的tomcat/mysql/redis容器也是启动状态的话，那么端口会冲突而启动失败 systemctl restart docker cd /usr/local/mycompose docker-compose up # 如果后台启动则使用如下命令 docker-compose up -d # 若要停止 docker-compose stop可以查看到命令中，创建了两个自定义的网络（mycompose_dev和mycompose_pro），然后创建容器，并将网络应用到服务上。可以查看一下具体的网络，使用 docker network ls 如下：查看启动的容器：5.5.3 测试在windows下访问启动的3个服务进行测试都可以；如下面访问9090的tomcat如下：6 迁移与备份其中涉及到的命令有：docker commit 将容器保存为镜像docker save 将镜像备份为tar文件docker load 根据tar文件恢复为镜像6.1 将Docker容器保存为镜像使用docker commit命令可以将容器保存为镜像。命令形式：docker commit 容器名称 镜像名称# 保存nginx容器为镜像 docker commit mynginx mynginx此镜像的内容就是当前容器的内容，接下来你可以用此镜像再次运行新的容器6.2 镜像备份使用docker save命令可以将已有镜像保存为tar 文件。命令形式：docker save –o tar文件名 镜像名# 保存镜像为文件 docker save -o mynginx.tar mynginx6.3 镜像恢复与迁移使用docker load命令可以根据tar文件恢复为docker镜像。命令形式：docker load -i tar文件名# 停止mynginx容器 docker stop mynginx # 删除mynginx容器 docker rm mynginx # 删除mynginx镜像 docker rmi mynginx # 加载恢复mynginx镜像 docker load -i mynginx.tar # 在镜像恢复之后，基于该镜像再次创建启动容器 docker run -di --name=mynginx -p 80:80 mynginx注意：在执行docker load命令恢复镜像时，需要先删除原镜像。7 Dockerfile文件7.1 什么是Dockerfile文件Dockerfile其实就是一个文本文件，由一系列命令和参数构成，Docker可以读取Dockerfile文件并根据Dockerfile文件的描述来构建镜像。Dockerfile文件内容一般分为4部分：基础镜像信息维护者信息镜像操作指令容器启动时执行的指令7.2 Dockerfile常用命令7.3 使用Dockerfile创建镜像# 1、创建目录 mkdir –p /usr/local/dockerjdk8 cd /usr/local/dockerjdk8 # 2、下载jdk-8u202-linux-x64.tar.gz并上传到服务器（虚拟机）的/usr/local/dockerjdk8目录 # 3、在/usr/local/dockerjdk8目录下创建Dockerfile文件，文件内容如下： vi Dockerfile FROM centos:7 MAINTAINER FANFAN WORKDIR /usr RUN mkdir /usr/local/java ADD jdk-8u202-linux-x64.tar.gz /usr/local/java/ ENV JAVA_HOME /usr/local/java/jdk1.8.0_202 ENV JRE_HOME $JAVA_HOME/jre ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH ENV PATH $JAVA_HOME/bin:$PATH # 4、执行命令构建镜像；不要忘了后面的那个 . docker build -t='jdk1.8' . # 5、查看镜像是否建立完成 docker images7.4 基于镜像创建容器基于刚刚创建的镜像 jdk1.8 创建并启动容器进行测试；# 创建并启动容器 docker run -it --name=testjdk jdk1.8 /bin/bash # 在容器中测试jdk是否已经安装 java -version8 Docker私有仓库8.1 私有仓库搭建与配置Docker官方的Docker hub（https://hub.docker.com）是一个用于管理公共镜像的仓库，我们可以从上面拉取镜像到本地，也可以把我们自己的镜像推送上去。但是，有时候我们的服务器无法访问互联网，或者你不希望将自己的镜像放到公网当中，那么我们就需要搭建自己的私有仓库来存储和管理自己的镜像。私有仓库搭建步骤：# 1、拉取私有仓库镜像 docker pull registry # 2、启动私有仓库容器 docker run -di --name=registry -p 5000:5000 registry # 3、打开浏览器 输入地址http://宿主机ip:5000/v2/_catalog，看到{"repositories":[]} 表示私有仓库搭建成功 # 4、修改daemon.json vi /etc/docker/daemon.json # 在上述文件中添加一个key，保存退出。此步用于让 docker 信任私有仓库地址；注意将宿主机ip修改为自己宿主机真实ip {"insecure-registries":["宿主机ip:5000"]} # 5、重启docker 服务 systemctl restart docker docker start registry8.2 将镜像上传至私有仓库操作步骤：# 1、标记镜像为私有仓库的镜像 docker tag jdk1.8 宿主机IP:5000/jdk1.8 # 2、再次启动私有仓库容器 docker restart registry # 3、上传标记的镜像 docker push 宿主机IP:5000/jdk1.8 # 4、输入网址查看仓库效果8.3 从私有仓库拉取镜像若是在私有仓库所在的服务器上去拉取镜像；那么直接执行如下命令：# 因为私有仓库所在的服务器上已经存在相关镜像；所以先删除；请指定镜像名，不是id docker rmi 服务器ip:5000/jdk1.8 #拉取镜像 docker pull 服务器ip:5000/jdk1.8 #可以通过如下命令查看 docker 的信息；了解到私有仓库地址 docker info执行 docker info 命令之后显示内容：整体docker的私有仓库运作形如下图：
1 Git概述1.1 Git历史Git 诞生于一个极富纷争大举创新的年代。Linux 内核开源项目有着为数众多的参与者。 绝大多数的 Linux 内核维护工作都花在了提交补丁和保存归档的繁琐事务上（1991－2002年间）。 到 2002 年，整个项目组开始启用一个专有的分布式版本控制系统 BitKeeper 来管理和维护代码。到了 2005 年，开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束，他们收回了 Linux 内核社区免费使用 BitKeeper 的权力。 这就迫使 Linux 开源社区（特别是 Linux 的缔造者 Linus Torvalds）基于使用 BitKeeper 时的经验教训，开发出自己的版本系统。 他们对新的系统制订了若干目标：速度简单的设计对非线性开发模式的强力支持（允许成千上万个并行开发的分支）完全分布式有能力高效管理类似 Linux 内核一样的超大规模项目（速度和数据量）1.2 Git与SVN对比SVN是集中式版本控制系统，版本库是集中放在中央服务器的，而开发人员工作的时候，用的都是自己的电脑，所以首先要从中央服务器下载最新的版本，然后开发，开发完后，需要把自己开发的代码提交到中央服务器。集中式版本控制工具缺点：服务器单点故障容错性差Git是分布式版本控制系统（Distributed Version Control System，简称 DVCS） ，分为两种类型的仓库：本地仓库和远程仓库本地仓库：是在开发人员自己电脑上的Git仓库 远程仓库：是在远程服务器上的Git仓库Clone：克隆，就是将远程仓库复制到本地Push：推送，就是将本地仓库代码上传到远程仓库Pull：拉取，就是将远程仓库代码下载到本地仓库1.3 Git工作流程工作流程如下：1．从远程仓库中克隆代码到本地仓库2．从本地仓库中checkout代码然后进行代码修改3．在提交前先将代码提交到暂存区4．提交到本地仓库。本地仓库中保存修改的各个历史版本5．修改完成后，需要和团队成员共享代码时，将代码push到远程仓库1.4 Git下载与安装下载地址： https://git-scm.com/download安装过程详见：https://ffis.me/experience/1441.html2 Git代码托管服务2.1 常用的Git代码托管服务前面已经知道了Git中存在两种类型的仓库，即本地仓库和远程仓库。那么我们如何搭建Git远程仓库呢？我们可以借助互联网上提供的一些代码托管服务来实现，其中比较常用的有GitHub、码云、GitLab等。Github（ 地址：https://github.com/ ）是一个面向开源及私有软件项目的托管平台，因为只支持Git 作为唯一的版本库格式进行托管，故名Github码云（地址： https://gitee.com/ ）是国内的一个代码托管平台，由于服务器在国内，所以相比于GitHub，码云速度会更快GitLab （地址： https://about.gitlab.com/ ）是一个用于仓库管理系统的开源项目，使用Git作为代码管理工具，并在此基础上搭建起来的web服务2.2 在码云注册账号要想使用码云的相关服务，需要注册账号（地址： https://gitee.com/signup ）2.3 登录码云并创建Git远程仓库注册完成后就可以使用刚刚注册的邮箱进行登录（地址： https://gitee.com/login ）登录成功后就可以创建Git远程仓库创建完成后可以查看仓库信息每个Git远程仓库都会对应一个网络地址，可以点击克隆/下载按钮弹出窗口并点击复制按钮获得这个网络地址当前创建的Git远程仓库对应的地址为：https://gitee.com/noisky/myRepo.git2.4 邀请其他用户成为仓库成员前面已经在码云上创建了自己的远程仓库，目前仓库成员只有自己一个人（身份为管理员）。在企业实际开发中，一个项目往往是由多个人共同开发完成的，为了使多个参与者都有权限操作远程仓库，就需要邀请其他项目参与者成为当前仓库的成员。3 Git常用命令3.1 环境配置当安装Git后首先要做的事情是设置用户名称和email地址。这是非常重要的，因为每次Git提交都会使用该用户信息设置用户信息 git config --global user.name “noisky”git config --global user.email “xxx@ffis.me”查看配置信息git config --listgit config user.name通过上面的命令设置的信息会保存在~/.gitconfig（用户目录下）文件中3.2 获取Git仓库要使用Git对我们的代码进行版本控制，首先需要获得Git仓库获取Git仓库通常有两种方式：在本地初始化一个Git仓库从远程仓库克隆3.2.1 在本地初始化一个Git仓库执行步骤如下：1.在电脑的任意位置创建一个空目录（例如repo1）作为我们的本地Git仓库2.进入这个目录中，点击右键打开Git bash窗口3.执行命令git init如果在当前目录中看到.git文件夹（此文件夹为隐藏文件夹）则说明Git仓库创建成功3.2.2 从远程仓库克隆可以通过Git提供的命令从远程仓库进行克隆，将远程仓库克隆到本地命令形式为：git clone 远程Git仓库地址 3.3 工作目录、暂存区以及版本库概念为了更好的学习Git，我们需要了解Git相关的一些概念，这些概念在后面的学习中会经常提到版本库：前面看到的.git隐藏文件夹就是版本库，版本库中存储了很多配置信息、日志信息和文件版本信息等工作目录（工作区）：包含.git文件夹的目录就是工作目录，主要用于存放开发的代码暂存区：.git文件夹中有很多文件，其中有一个index文件就是暂存区，也可以叫做stage。暂存区是一个临时保存修改文件的地方3.4 Git工作目录下文件的两种状态Git工作目录下的文件存在两种状态：untracked 未跟踪（未被纳入版本控制）tracked 已跟踪（被纳入版本控制）Unmodified 未修改状态Modified 已修改状态Staged 已暂存状态这些文件的状态会随着我们执行Git的命令发生变化3.5 本地仓库操作git status 查看文件状态也可以使用git status –s使输出信息更加简洁git add 将未跟踪的文件加入暂存区将新创建的文件加入暂存区后查看文件状态git reset 将暂存区的文件取消暂存将文件取消暂存后查看文件状态git commit 将暂存区的文件修改提交到本地仓库git rm 删除文件删除文件后查看文件状态上面删除的只是工作区的文件，需要提交到本地仓库将文件添加至忽略列表一般我们总会有些文件无需纳入Git 的管理，也不希望它们总出现在未跟踪文件列表。 通常都是些自动生成的文件，比如日志文件，或者编译过程中创建的临时文件等。 在这种情况下，我们可以在工作目录中创建一个名为 .gitignore 的文件（文件名称固定），列出要忽略的文件模式。下面是一个示例：# no .a files *.a # but do track lib.a, even though you're ignoring .a files above !lib.a # only ignore the TODO file in the current directory, not subdir/TODO /TODO # ignore all files in the build/ directory build/ # ignore doc/notes.txt, but not doc/server/arch.txt doc/*.txt # ignore all .pdf files in the doc/ directory doc/**/*.pdfgit log 查看日志记录3.6 远程仓库操作前面执行的命令操作都是针对的本地仓库，下面介绍远程仓库的一些操作，具体包括：3.6.1 查看远程仓库如果想查看已经配置的远程仓库服务器，可以运行 git remote 命令。 它会列出指定的每一个远程服务器的简写。 如果已经克隆了远程仓库，那么至少应该能看到 origin ，这是 Git 克隆的仓库服务器的默认名字3.6.2 添加远程仓库运行 git remote add 添加一个新的远程 Git 仓库，同时指定一个可以引用的简写3.6.3 从远程仓库克隆如果你想获得一份已经存在了的 Git 仓库的拷贝，这时就要用到 git clone 命令。 Git 克隆的是该 Git 仓库服务器上的几乎所有数据（包括日志信息、历史记录等），而不仅仅是复制工作所需要的文件。 当你执行 git clone 命令的时候，默认配置下远程 Git 仓库中的每一个文件的每一个版本都将被拉取下来。克隆仓库的命令格式是 git clone [url] 3.6.4 移除无效的远程仓库如果因为一些原因想要移除一个远程仓库 ，可以使用 git remote rm注意：此命令只是从本地移除远程仓库的记录，并不会真正影响到远程仓库3.6.5 从远程仓库中抓取与拉取git fetch 是从远程仓库获取最新版本到本地仓库，不会自动mergegit pull 是从远程仓库获取最新版本并merge到本地仓库注意：如果当前本地仓库不是从远程仓库克隆，而是本地创建的仓库，并且仓库中存在文件，此时再从远程仓库拉取文件的时候会报错（fatal: refusing to merge unrelated histories ）解决此问题可以在git pull命令后加入参数 --allow-unrelated-histories3.6.6 推送到远程仓库当你想分享你的代码时，可以将其推送到远程仓库。 命令形式：git push remote-name3.7 Git分支几乎所有的版本控制系统都以某种形式支持分支。 使用分支意味着你可以把你的工作从开发主线上分离开来，以免影响开发主线。Git 的master分支并不是一个特殊分支。 它跟其它分支没有区别。 之所以几乎每一个仓库都有 master 分支，是因为git init 命令默认创建它，并且大多数人都懒得去改动它。下面介绍关于分支的相关命令，具体如下：3.7.1 查看分支\# 列出所有本地分支$ git branch\# 列出所有远程分支$ git branch -r\# 列出所有本地分支和远程分支$ git branch -a3.7.2 创建分支3.7.3 切换分支​3.7.4 推送至远程仓库分支3.7.5 合并分支如将b1分支合并到master分支，先切换到master分支，然后执行git merge b1命令有时候合并操作不会如此顺利。 如果你在两个不同的分支中，对同一个文件的同一个部分进行了不同的修改，Git 就没办法合并它们，同时会提示文件冲突。此时需要我们打开冲突的文件并修复冲突内容，最后执行git add命令来标识冲突已解决3.7.5 删除分支如果要删除的分支中进行了一些开发动作，此时执行上面的删除命令并不会删除分支，如果坚持要删除此分支，可以将命令中的-d参数改为-D注：如果要删除远程仓库中的分支，可以使用命令git push origin –d branchName3.8 标签-扩展像其他版本控制系统（VCS）一样，Git 可以给历史中的某一个提交打上标签，以示重要。 比较有代表性的是人们会使用这个功能来标记发布结点（v1.0 、v1.2等）。标签指的是某个分支某个特定时间点的状态。通过标签，可以很方便的切换到标记时的状态。在本节中，我们将学习：列出已有的标签创建新标签将标签推送至远程仓库检出标签删除标签3.8.1 列出已有的标签\# 列出所有tag$ git tag\# 查看tag信息$ git show [tag]3.8.2 创建新标签\# 新建一个tag$ git tag [tagName]3.8.3 将标签推送至远程仓库\# 提交指定tag$ git push [remote] [tag]3.8.4 检出标签\# 新建一个分支，指向某个tag$ git checkout -b [branch] [tag]3.8.5 删除标签\# 删除本地tag$ git tag -d [tag]\# 删除远程tag$ git push origin :refs/tags/[tag]4 在IDEA中使用Git4.1 在IDEA中配置Git安装好IntelliJ IDEA后，如果Git安装在默认路径下，那么idea会自动找到git的位置，如果更改了Git的安装位置则需要手动配置下Git的路径。选择File→Settings打开设置窗口，找到Version Control下的git选项：选择git的安装目录后可以点击“Test”按钮测试是否正确配置4.2 在IDEA中使用Git4.2.1在IDEA中创建工程并将工程添加至Git将项目添加至Git管理后，可以从IDEA的工具栏上看到Git操作的按钮4.2.2 将文件添加到暂存区4.2.3 提交文件4.2.4 将代码推送到远程仓库4.2.5 从远程仓库克隆工程到本地4.2.6 从远程拉取代码4.2.7 版本对比4.2.8 创建分支4.2.9 切换分支4.2.10 分支合并
1 Mybatis多表查询1.1 一对一查询1.1.1 一对一查询的模型用户表和订单表的关系为，一个用户有多个订单，一个订单只从属于一个用户一对一查询的需求：查询一个订单，与此同时查询出该订单所属的用户1.1.2一对一查询的语句对应的sql语句：SELECT *,o.id oid FROM orders o, USER u WHERE o.uid = u.id;查询的结果如下：1.1.3 创建Order和User实体public class Order { private int id; private Date ordertime; private double total; //代表当前订单从属于哪一个客户 private User user; //此处省略getter和setter } public class User { private int id; private String username; private String password; private Date birthday; //此处省略getter和setter }1.1.4 创建OrderMapper接口public interface OrderMapper { List<Order> findAll(); }1.1.5 配置OrderMapper.xml<mapper namespace="me.ffis.mapper.OrderMapper"> <resultMap id="orderMap" type="me.ffis.domain.Order"> <result column="uid" property="user.id"></result> <result column="username" property="user.username"></result> <result column="password" property="user.password"></result> <result column="birthday" property="user.birthday"></result> </resultMap> SELECT *,o.id oid FROM orders o, USER u WHERE o.uid = u.id </mapper>其中还可以配置如下：<resultMap id="orderMap" type="me.ffis.domain.Order"> <result property="id" column="id"></result> <result property="ordertime" column="ordertime"></result> <result property="total" column="total"></result> <association property="user" javaType="me.ffis.domain.User"> <result column="uid" property="id"></result> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="birthday" property="birthday"></result> </association> </resultMap>1.1.6 测试结果OrderMapper mapper = sqlSession.getMapper(OrderMapper.class); List<Order> all = mapper.findAll(); for(Order order : all){ System.out.println(order); }1.2 一对多查询1.2.1 一对多查询的模型用户表和订单表的关系为，一个用户有多个订单，一个订单只从属于一个用户一对多查询的需求：查询一个用户，与此同时查询出该用户具有的订单1.2.2 一对多查询的语句对应的sql语句：SELECT *,o.id oid FROM USER u, orders o WHERE u.id = o.uid;查询的结果如下：1.2.3 修改User实体public class Order { private int id; private Date ordertime; private double total; //代表当前订单从属于哪一个客户 private User user; //此处省略getter和setter } public class User { private int id; private String username; private String password; private Date birthday; //代表当前用户具备哪些订单 private List<Order> orderList; //此处省略getter和setter }1.2.4 创建UserMapper接口public interface UserMapper { List<User> findAll(); }1.2.5 配置UserMapper.xml<mapper namespace="me.ffis.mapper.UserMapper"> <resultMap id="userMap" type="me.ffis.domain.User"> <result column="id" property="id"></result> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="birthday" property="birthday"></result> <collection property="orderList" ofType="me.ffis.domain.Order"> <result column="oid" property="id"></result> <result column="ordertime" property="ordertime"></result> <result column="total" property="total"></result> </collection> </resultMap> select *,o.id oid from user u left join orders o on u.id=o.uid </mapper>1.2.6 测试结果UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> all = mapper.findAll(); for(User user : all){ System.out.println(user.getUsername()); List<Order> orderList = user.getOrderList(); for(Order order : orderList){ System.out.println(order); } System.out.println("----------------------------------"); }1.3 多对多查询1.3.1 多对多查询的模型用户表和角色表的关系为，一个用户有多个角色，一个角色被多个用户使用多对多查询的需求：查询用户同时查询出该用户的所有角色1.3.2 多对多查询的语句对应的sql语句：SELECT u.*,r.*,r.id rid FROM USER u LEFT JOIN sys_user_role sr ON u.id=sr.userid INNER JOIN sys_role r ON sr.roleid=r.id;查询的结果如下：1.3.3 创建Role实体，修改User实体public class User { private int id; private String username; private String password; private Date birthday; //代表当前用户具备哪些订单 private List<Order> orderList; //代表当前用户具备哪些角色 private List<Role> roleList; //此处省略getter和setter } public class Role { private int id; private String rolename; //此处省略getter和setter } 1.3.4 添加UserMapper接口方法List<User> findAllUserAndRole();1.3.5 配置UserMapper.xml<resultMap id="userRoleMap" type="me.ffis.domain.User"> <result column="id" property="id"></result> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="birthday" property="birthday"></result> <collection property="roleList" ofType="me.ffis.domain.Role"> <result column="rid" property="id"></result> <result column="rolename" property="rolename"></result> </collection> </resultMap> SELECT u.*,r.*,r.id rid FROM USER u LEFT JOIN sys_user_role sr ON u.id=sr.userid INNER JOIN sys_role r ON sr.roleid=r.id; 1.3.6 测试结果UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> all = mapper.findAllUserAndRole(); for(User user : all){ System.out.println(user.getUsername()); List<Role> roleList = user.getRoleList(); for(Role role : roleList){ System.out.println(role); } System.out.println("----------------------------------"); }1.4 知识小结MyBatis多表配置方式：一对一配置：使用做配置一对多配置：使用+做配置多对多配置：使用+做配置2 Mybatis的注解开发2.1 MyBatis的常用注解这几年来注解开发越来越流行，Mybatis也可以使用注解开发方式，这样我们就可以减少编写Mapper映射文件了。我们先围绕一些基本的CRUD来学习，再学习复杂映射多表操作。@Insert：实现新增@Update：实现更新@Delete：实现删除@Select：实现查询@Result：实现结果集封装@Results：可以与@Result 一起使用，封装多个结果集@One：实现一对一结果集封装@Many：实现一对多结果集封装2.2 MyBatis的增删改查我们完成简单的user表的增删改查的操作private UserMapper userMapper; @Before public void before() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(true); userMapper = sqlSession.getMapper(UserMapper.class); } @Test public void testAdd() { User user = new User(); user.setUsername("测试数据"); user.setPassword("123"); user.setBirthday(new Date()); userMapper.add(user); } @Test public void testUpdate() throws IOException { User user = new User(); user.setId(16); user.setUsername("测试数据修改"); user.setPassword("abc"); user.setBirthday(new Date()); userMapper.update(user); } @Test public void testDelete() throws IOException { userMapper.delete(16); } @Test public void testFindById() throws IOException { User user = userMapper.findById(1); System.out.println(user); } @Test public void testFindAll() throws IOException { List<User> all = userMapper.findAll(); for(User user : all){ System.out.println(user); } }修改MyBatis的核心配置文件，我们使用了注解替代的映射文件，所以我们只需要加载使用了注解的Mapper接口即可<mappers> <mapper class="me.ffis.mapper.UserMapper"></mapper> </mappers>或者指定扫描包含映射关系的接口所在的包也可以<mappers> <package name="me.ffis.mapper"></package> </mappers>2.3 MyBatis的注解实现复杂映射开发实现复杂关系映射之前我们可以在映射文件中通过配置来实现，使用注解开发后，我们可以使用@Results注解，@Result注解，@One注解，@Many注解组合完成复杂关系的配置2.4 一对一查询2.4.1 一对一查询的模型用户表和订单表的关系为，一个用户有多个订单，一个订单只从属于一个用户一对一查询的需求：查询一个订单，与此同时查询出该订单所属的用户2.4.2 一对一查询的语句对应的sql语句：select * from orders; select * from user where id=查询出订单的uid; 查询的结果如下：2.4.3 创建Order和User实体public class Order { private int id; private Date ordertime; private double total; //代表当前订单从属于哪一个客户 private User user; //此处省略getter和setter } public class User { private int id; private String username; private String password; private Date birthday; //此处省略getter和setter }2.4.4 创建OrderMapper接口public interface OrderMapper { List<Order> findAll(); }2.4.5 使用注解配置Mapperpublic interface OrderMapper { @Select("select * from orders") @Results({ @Result(id=true,property = "id",column = "id"), @Result(property = "ordertime",column = "ordertime"), @Result(property = "total",column = "total"), @Result(property = "user",column = "uid", javaType = User.class, one = @One(select = "me.ffis.mapper.UserMapper.findById")) }) List<Order> findAll(); }public interface UserMapper { @Select("select * from user where id=#{id}") User findById(int id); }2.4.6 测试结果@Test public void testSelectOrderAndUser() { List<Order> all = orderMapper.findAll(); for(Order order : all){ System.out.println(order); } }2.5 一对多查询2.5.1 一对多查询的模型用户表和订单表的关系为，一个用户有多个订单，一个订单只从属于一个用户一对多查询的需求：查询一个用户，与此同时查询出该用户具有的订单2.5.2 一对多查询的语句对应的sql语句：select * from user; select * from orders where uid=查询出用户的id; 查询的结果如下：2.5.3 修改User实体public class Order { private int id; private Date ordertime; private double total; //代表当前订单从属于哪一个客户 private User user; //此处省略getter和setter } public class User { private int id; private String username; private String password; private Date birthday; //代表当前用户具备哪些订单 private List<Order> orderList; //此处省略getter和setter }2.5.4 创建UserMapper接口List<User> findAllUserAndOrder();2.5.5 使用注解配置Mapperpublic interface UserMapper { @Select("select * from user") @Results({ @Result(id = true,property = "id",column = "id"), @Result(property = "username",column = "username"), @Result(property = "password",column = "password"), @Result(property = "birthday",column = "birthday"), @Result(property = "orderList",column = "id", javaType = List.class, many = @Many(select = "me.ffis.mapper.OrderMapper.findByUid")) }) List<User> findAllUserAndOrder(); } public interface OrderMapper { @Select("select * from orders where uid=#{uid}") List<Order> findByUid(int uid); }2.5.6 测试结果List<User> all = userMapper.findAllUserAndOrder(); for(User user : all){ System.out.println(user.getUsername()); List<Order> orderList = user.getOrderList(); for(Order order : orderList){ System.out.println(order); } System.out.println("-----------------------------"); }2.6 多对多查询2.6.1 多对多查询的模型用户表和角色表的关系为，一个用户有多个角色，一个角色被多个用户使用多对多查询的需求：查询用户同时查询出该用户的所有角色2.6.2 多对多查询的语句对应的sql语句：select * from user; select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=用户的id 查询的结果如下：2.6.3 创建Role实体，修改User实体public class User { private int id; private String username; private String password; private Date birthday; //代表当前用户具备哪些订单 private List<Order> orderList; //代表当前用户具备哪些角色 private List<Role> roleList; //此处省略getter和setter } public class Role { private int id; private String rolename; //此处省略getter和setter }2.6.4 添加UserMapper接口方法List<User> findAllUserAndRole();2.6.5 使用注解配置Mapperpublic interface UserMapper { @Select("select * from user") @Results({ @Result(id = true,property = "id",column = "id"), @Result(property = "username",column = "username"), @Result(property = "password",column = "password"), @Result(property = "birthday",column = "birthday"), @Result(property = "roleList",column = "id", javaType = List.class, many = @Many(select = "me.ffis.mapper.RoleMapper.findByUid")) }) List<User> findAllUserAndRole();} public interface RoleMapper { @Select("select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=#{uid}") List<Role> findByUid(int uid); } 2.6.6 测试结果UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> all = mapper.findAllUserAndRole(); for(User user : all){ System.out.println(user.getUsername()); List<Role> roleList = user.getRoleList(); for(Role role : roleList){ System.out.println(role); } System.out.println("----------------------------------"); }SSM框架整合1.1 原始方式整合1.准备工作2.创建Maven工程3.导入Maven坐标4.编写实体类public class Account { private int id; private String name; private double money; //省略getter和setter方法 }5.编写Mapper接口public interface AccountMapper { //保存账户数据 void save(Account account); //查询账户数据 List<Account> findAll(); }6.编写Service接口public interface AccountService { void save(Account account); //保存账户数据 List<Account> findAll(); //查询账户数据 }7.编写Service接口实现@Service("accountService") public class AccountServiceImpl implements AccountService { public void save(Account account) { SqlSession sqlSession = MyBatisUtils.openSession(); AccountMapper accountMapper = sqlSession.getMapper(AccountMapper.class); accountMapper.save(account); sqlSession.commit(); sqlSession.close(); } public List<Account> findAll() { SqlSession sqlSession = MyBatisUtils.openSession(); AccountMapper accountMapper = sqlSession.getMapper(AccountMapper.class); return accountMapper.findAll(); } }8.编写Controller@Controller public class AccountController { @Autowired private AccountService accountService; @RequestMapping("/save") @ResponseBody public String save(Account account){ accountService.save(account); return "save success"; } @RequestMapping("/findAll") public ModelAndView findAll(){ ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("accountList"); modelAndView.addObject("accountList",accountService.findAll()); return modelAndView; } }9.编写添加页面<%@ page contentType="text/html;charset=UTF-8" language="java" %> Title 保存账户信息表单 用户名称 账户金额 10.编写列表页面 账户id 账户名称 账户金额 <c:forEach items="${accountList}" var="account"> ${account.id} ${account.name} ${account.money} </c:forEach> 11.编写相应配置文件(文件参考目录：素材/配置文件)Spring配置文件：applicationContext.xmlSprngMVC配置文件：spring-mvc.xmlMyBatis映射文件：AccountMapper.xmlMyBatis核心文件：sqlMapConfig.xml数据库连接信息文件：jdbc.propertiesWeb.xml文件：web.xml日志文件：log4j.xml12.测试添加账户1.2 Spring整合MyBatis1.整合思路2.将SqlSessionFactory配置到Spring容器中 <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"/> <property name="jdbcUrl" value="${jdbc.Url}"/> <property name="user" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:sqlMapConfig.xml"/> </bean>3.扫描Mapper，让Spring容器产生Mapper实现类 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="me.ffis.mapper"/> </bean>4.配置声明式事务控制 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:advice id="myAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> <bean id="timeAspect" class="me.ffis.aop.TimeAspect"/> <aop:config> <aop:pointcut id="implPoint" expression="execution(* me.ffis.service.impl.*.*(..))"/> <aop:advisor advice-ref="myAdvice" pointcut-ref="implPoint"/> <aop:aspect ref="timeAspect"> <aop:around method="getTime" pointcut-ref="implPoint"/> </aop:aspect> </aop:config>5.修改Service实现类代码@Service("accountService") public class AccountServiceImpl implements AccountService { @Autowired private AccountMapper accountMapper; public void save(Account account) { accountMapper.save(account); } public List<Account> findAll() { return accountMapper.findAll(); } }
1 Mybatis的Dao层实现1.1 传统开发方式1.1.1 编写UserDao接口public interface UserDao { List<User> findAll() throws IOException; }1.1.2 编写UserDaoImpl实现public class UserDaoImpl implements UserDao { public List<User> findAll() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); List<User> userList = sqlSession.selectList("userMapper.findAll"); sqlSession.close(); return userList; } }1.1.3 测试传统方式@Test public void testTraditionDao() throws IOException { UserDao userDao = new UserDaoImpl(); List<User> all = userDao.findAll(); System.out.println(all); }1.2 代理开发方式1.2.1 代理开发方式介绍采用 Mybatis 的代理开发方式实现 DAO 层的开发，这种方式是我们后面进入企业的主流。Mapper 接口开发方法只需要程序员编写Mapper 接口（相当于Dao 接口），由Mybatis 框架根据接口定义创建接口的动态代理对象，代理对象的方法体同上边Dao接口实现类方法。Mapper 接口开发需要遵循以下规范：1) Mapper.xml文件中的namespace与mapper接口的全限定名相同2) Mapper接口方法名和Mapper.xml中定义的每个statement的id相同3) Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同4) Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同1.2.2 编写UserMapper接口1.2.3 测试代理方式@Test public void testProxyDao() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); //获得MyBatis框架生成的UserMapper接口的实现类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.findById(1); System.out.println(user); sqlSession.close(); }1.3 知识小结MyBatis的Dao层实现的两种方式：手动对Dao进行实现：传统开发方式代理方式对Dao进行实现： **UserMapper userMapper = sqlSession.getMapper(UserMapper.class);** 2 MyBatis映射文件深入2.1 动态sql语句2.1.1 动态sql语句概述Mybatis 的映射文件中，前面我们的 SQL 都是比较简单的，有些时候业务逻辑复杂时，我们的 SQL是动态变化的，此时我们的 SQL 就不能满足要求了。参考的官方文档，描述如下：2.1.2 动态 SQL 之<if>我们根据实体类的不同取值，使用不同的 SQL语句来进行查询。比如在 id如果不为空时可以根据id查询，如果username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。 select * from User <where> <if test="id!=0"> and id=#{id} </if> <if test="username!=null"> and username=#{username} </if> </where> 当查询条件id和username都存在时，控制台打印的sql语句如下： … … … //获得MyBatis框架生成的UserMapper接口的实现类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User condition = new User(); condition.setId(1); condition.setUsername("lucy"); User user = userMapper.findByCondition(condition); … … …当查询条件只有id存在时，控制台打印的sql语句如下： … … … //获得MyBatis框架生成的UserMapper接口的实现类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User condition = new User(); condition.setId(1); User user = userMapper.findByCondition(condition); … … … 2.1.3 动态 SQL 之<foreach>循环执行sql的拼接操作，例如：SELECT * FROM USER WHERE id IN (1,2,5)。 select * from User <where> <foreach collection="array" open="id in(" close=")" item="id" separator=","> #{id} </foreach> </where> 测试代码片段如下：//获得MyBatis框架生成的UserMapper接口的实现类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); int[] ids = new int[]{2,5}; List<User> userList = userMapper.findByIds(ids); System.out.println(userList);foreach标签的属性含义如下：标签用于遍历集合，它的属性：collection：代表要遍历的集合元素，注意编写时不要写#{}open：代表语句的开始部分close：代表结束部分item：代表遍历集合的每个元素，生成的变量名sperator：代表分隔符2.2 SQL片段抽取Sql 中可将重复的 sql 提取出来，使用时用 include 引用即可，最终达到 sql 重用的目的 <sql id="selectUser">select * from User</sql> <include refid="selectUser"></include> where id=#{id} <include refid="selectUser"></include> <where> <foreach collection="array" open="id in(" close=")" item="id" separator=","> #{id} </foreach> </where> 2.3 知识小结MyBatis映射文件配置： ：查询 <insert>：插入 <update>：修改 <delete>：删除 <where>：where条件 <if>：if判断 <foreach>：循环 <sql>：sql片段抽取3 MyBatis核心配置文件深入3.1 typeHandlers标签无论是 MyBatis 在预处理语句（PreparedStatement）中设置一个参数时，还是从结果集中取出一个值时， 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器（截取部分）。你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。具体做法为：实现 org.apache.ibatis.type.TypeHandler 接口， 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler， 然后可以选择性地将它映射到一个JDBC类型。例如需求：一个Java中的Date数据类型，我想将之存到数据库的时候存成一个1970年至今的毫秒数，取出来时转换成java的Date，即java的Date与数据库的varchar毫秒值之间转换。开发步骤：①定义转换类继承类BaseTypeHandler②覆盖4个未实现的方法，其中setNonNullParameter为java程序设置数据到数据库的回调方法，getNullableResult为查询时 mysql的字符串类型转换成 java的Type类型的方法③在MyBatis核心配置文件中进行注册测试转换是否正确public class MyDateTypeHandler extends BaseTypeHandler<Date> { public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType type) { preparedStatement.setString(i,date.getTime()+""); } public Date getNullableResult(ResultSet resultSet, String s) throws SQLException { return new Date(resultSet.getLong(s)); } public Date getNullableResult(ResultSet resultSet, int i) throws SQLException { return new Date(resultSet.getLong(i)); } public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException { return callableStatement.getDate(i); } } <typeHandlers> <typeHandler handler="me.ffis.typeHandlers.MyDateTypeHandler"></typeHandler> </typeHandlers>测试添加操作：user.setBirthday(new Date()); userMapper.add2(user);数据库数据：测试查询操作：3.2 plugins标签MyBatis可以使用第三方的插件来对功能进行扩展，分页助手PageHelper是将分页的复杂操作进行封装，使用简单的方式即可获得分页的相关数据开发步骤：①导入通用PageHelper的坐标②在mybatis核心配置文件中配置PageHelper插件③测试分页数据获取①导入通用PageHelper坐标 <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>3.7.5</version> </dependency> <dependency> <groupId>com.github.jsqlparser</groupId> <artifactId>jsqlparser</artifactId> <version>0.9.1</version> </dependency>②在mybatis核心配置文件中配置PageHelper插件 <plugin interceptor="com.github.pagehelper.PageHelper"> <property name="dialect" value="mysql"/> </plugin>③测试分页代码实现@Test public void testPageHelper(){ //设置分页参数 PageHelper.startPage(1,2); List<User> select = userMapper2.select(null); for(User user : select){ System.out.println(user); } }获得分页相关的其他参数//其他分页的数据 PageInfo<User> pageInfo = new PageInfo<User>(select); System.out.println("总条数："+pageInfo.getTotal()); System.out.println("总页数："+pageInfo.getPages()); System.out.println("当前页："+pageInfo.getPageNum()); System.out.println("每页显示长度："+pageInfo.getPageSize()); System.out.println("是否第一页："+pageInfo.isIsFirstPage()); System.out.println("是否最后一页："+pageInfo.isIsLastPage());3.3 知识小结MyBatis核心配置文件常用标签：1、properties标签：该标签可以加载外部的properties文件2、typeAliases标签：设置类型别名3、environments标签：数据源环境配置标签4、typeHandlers标签：配置自定义类型处理器5、plugins标签：配置MyBatis的插件