前言

Hangfire是一个开源且商业免费使用的工具函数库。可以让你非常容易地在ASP.NET应用(也可以不在ASP.NET应用)中执行多种类型的后台任务,而无需自行定制开发和管理基于Windows Service后台任务执行器。且任务信息可以被持久保存。内置提供集成化的控制台。 原文

Hangfire目前资料不多,官方文档提供两个教程 Sending Mail in Background with ASP.NET MVC 和 Highlighter Tutorial,根据第二个教程山寨了一把,把文中的Entity Framework 改成了 Dapper,Sql Server数据库改成了LocalDB。结合自己的理解把原文翻译一下(第一次翻译,如有不妥尽情拍砖)

Simple sample :https://github.com/odinserj/Hangfire.Highlighter

Full sample:http://highlighter.hangfire.iosources

目录

  • Overview 概述
  • Setting up the project 设置项目

    • Prerequisites  前置条件
    • Creating a project 创建项目
    • Hiliting the code  醒目代码
  • The problem
  • Solving a problem
    • Installing Hangfire
    • Moving to background
  • Conclusion

概述

  考虑到你正在构建一个像GitHub Gists的代码片段库web应用程序,并且想实现语法高亮的特性。为了提供用户体验,甚至你想让它在一个用户禁用JavaScript的浏览器中也能正常工作。

  为了支持这种场景,并且缩短项目开发时间,你会选择使用一个Web Service实现语法高亮,比如:http://pygments.appspot.com or http://www.hilite.me.

Note

Although this feature can be implemented without web services (using different syntax highlighter libraries for .NET), we are using them just to show some pitfalls regarding to their usage in web applications.

You can substitute this example with real-world scenario, like using external SMTP server, another services or even long-running CPU-intensive task. 这段不翻译了,难。

设置项目

Tip

This section contains steps to prepare the project. However, if you don’t want to do the boring stuff or if you have problems with project set-up, you can download the tutorial source code and go straight to The problem section.

前置条件

这个教程使用VS2012 (安装了Web Tools 2013 for Visual Studio 2012 补丁),你也可以使用VS2013。这个项目用到了.NET4.5,ASP.NET MVC5 和SQL Server 2008 Express 或更高版本。

注意:那个补丁就是为了在创建项目时有Asp.net MVC5 Empty Project 这个默认的项目模板,我下载了安装报错,找不到对应的包。SqlServer 2008 是为了存储代码片段和Hangfire的任务数据,他还用到了EF做为orm框架。

我这里使用VS2013+LocalDB+Dapper实现类似功能。

创建项目

打开VS2013->Web->ASP.NET Web 应用程序->弹出框里选择 MVC 模板,身份验证 我改成了 无身份验证

创建完成之后默认就有个HomeController.cs 控制器。其中的Index Action 看起来是这样的:

  public class HomeController : Controller
{
public ActionResult Index()
{
return View();
} public ActionResult About()
{
ViewBag.Message = "Your application description page."; return View();
} public ActionResult Contact()
{
ViewBag.Message = "Your contact page."; return View();
}
}

默认的Index视图也存在了,可以直接F5运行,看看是不是显示了默认页面。

定义模型类(Defining a model )

在Models文件夹下创建CodeSnippet.cs 文件

     public class CodeSnippet
{
public int Id { get; set; } [Required, AllowHtml, Display(Name = "C# source")]
public string SourceCode { get; set; }
public string HighlightedCode { get; set; } public DateTime CreatedAt { get; set; }
public DateTime? HighlightedAt { get; set; }
}

CodeSnippet

接下来官网文档是安装EntityFramework包,我这里使用整理过的Dapper+DapperExtensions实现的简易ORM(参照地址 ),文件放在Data文件夹下,如:

创建一个Services 文件夹,创建 HighLightRepository.cs 文件

    /// <summary>
/// 仓储类
/// </summary>
public class HighLightRepository : BaseRepository, IDisposable
{
public static readonly HighLightRepository Instance = new HighLightRepository(); private HighLightRepository()
: this(SessionHelper.CreateDefaultSession())
{
} public HighLightRepository(IDBSession dbSession)
: base(dbSession)
{
}
} /// <summary>
/// 数据库连接帮助类
/// </summary>
public class SessionHelper
{
/// <summary>
/// 创建默认数据库连接会话
/// </summary>
/// <param name="connName"></param>
/// <returns></returns>
public static DBSession CreateDefaultSession(string connName = "DefaultConnection")
{
var connection = SqlConnectionFactory.CreateSqlConnection(DatabaseType.SqlServer, connName); return new DBSession(new Database(connection));
}
} /// <summary>
/// 仓储基类
/// </summary>
public abstract class BaseRepository : RepositoryDataImpl, IDisposable
{
protected BaseRepository()
{
SetDBSession(SessionHelper.CreateDefaultSession());
} protected BaseRepository(IDBSession dbSession)
: base(dbSession)
{
} public void Dispose()
{
}
}

HighLightRepository.cs

这里用到的连接字符串名字是 DefaultConnection。

创建LocalDB数据库文件(Defining a model )

在vs2013中打开SQL Server 对象资源管理器,右键“添加 Sql Server”,Server name 后面输入 (localdb)\v11.0.

右键添加数据库ZereoesHangfire,数据库位置 我这里选择了App_Data 文件夹。

新建数据库表CodeSnippet,执行是点击“更新”按钮,弹出一个sql确认框,运行后就可以创建表了,默认用的是dbo架构。

Our database is ready to use! 上面这部分没有按照文档中的操作。

创建视图和动作(Creating actions and views)

现在是向我们的项目注入活力的时候了,请按照描述的样子修改文件。

  public class HomeController : Controller
{
public ActionResult Index()
{ var snippetCodeList = HighLightRepository.Instance.GetList<CodeSnippet>(); return View(snippetCodeList);
} public ActionResult Details(int id)
{
var snippet = HighLightRepository.Instance.GetById<CodeSnippet>(id);
return View(snippet);
} public ActionResult Create()
{
return View();
} [HttpPost]
public ActionResult Create([Bind(Include = "SourceCode")] CodeSnippet snippet)
{
try
{
if (ModelState.IsValid)
{
snippet.CreatedAt = DateTime.UtcNow;
// We'll add the highlighting a bit later. //方案一:直接调用接口实现高亮
//using (StackExchange.Profiling.MiniProfiler.StepStatic("Service call"))
//{
// snippet.HighlightedCode = HighlightSource(snippet.SourceCode);
// snippet.HighlightedAt = DateTime.Now;
//} HighLightRepository.Instance.Insert(snippet); return RedirectToAction("Details", new { id = snippet.Id });
} return View(snippet); }
catch (HttpRequestException)
{
ModelState.AddModelError("", "Highlighting service returned error. Try again later.");
}
return View(snippet);
} protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
}

HomeController

 @* ~/Views/Home/Index.cshtml *@

 @model IEnumerable< Zeroes.Snippet.Highlight.Models.CodeSnippet>
@{ ViewBag.Title = "Snippets"; } <h2>Snippets</h2> <p><a class="btn btn-primary" href="@Url.Action("Create")">Create Snippet</a></p>
<table class="table">
<tr>
<th>Code</th>
<th>Created At</th>
<th>Highlighted At</th>
</tr> @foreach (var item in Model)
{
<tr>
<td>
<a href="@Url.Action("Details", new { id = item.Id })">@Html.Raw(item.HighlightedCode)</a>
</td>
<td>@item.CreatedAt</td>
<td>@item.HighlightedAt</td>
</tr>
}
</table>

Index.cshtml

 @* ~/Views/Home/Create.cshtml *@

 @model  Zeroes.Snippet.Highlight.Models.CodeSnippet
@{ ViewBag.Title = "Create a snippet"; } <h2>Create a snippet</h2> @using (Html.BeginForm())
{
@Html.ValidationSummary(true) <div class="form-group">
@Html.LabelFor(model => model.SourceCode)
@Html.ValidationMessageFor(model => model.SourceCode)
@Html.TextAreaFor(model => model.SourceCode, new { @class = "form-control", style = "min-height: 300px;", autofocus = "true" })
</div> <button type="submit" class="btn btn-primary">Create</button>
<a class="btn btn-default" href="@Url.Action("Index")">Back to List</a>
}

Create.cshtml

 @* ~/Views/Home/Details.cshtml *@

 @model   Zeroes.Snippet.Highlight.Models.CodeSnippet
@{ ViewBag.Title = "Details"; } <h2>Snippet <small>#@Model.Id</small></h2> <div>
<dl class="dl-horizontal">
<dt>@Html.DisplayNameFor(model => model.CreatedAt)</dt>
<dd>@Html.DisplayFor(model => model.CreatedAt)</dd>
<dt>@Html.DisplayNameFor(model => model.HighlightedAt)</dt>
<dd>@Html.DisplayFor(model => model.HighlightedAt)</dd>
</dl> <div class="clearfix"></div>
</div>
<div>@Html.Raw(Model.HighlightedCode)</div>

Details.cshtml

添加MiniProfiler

Install-Package MiniProfiler,我一般使用界面添加,不在控制台下操作。

安装后,修改Global.asax.cs文件,添加如下代码:

        protected void Application_BeginRequest()
{
StackExchange.Profiling.MiniProfiler.Start();
}
protected void Application_EndRequest()
{
StackExchange.Profiling.MiniProfiler.Stop();
}

修改_Layout.cshtml

<head>
<!-- ... -->
@StackExchange.Profiling.MiniProfiler.RenderIncludes()
</head>

修改web.config

  <!--下面是手动添加的-->
<system.webServer>
<handlers>
<add name="MiniProfiler" path="mini-profiler-resources/*" verb="*" type="System.Web.Routing.UrlRoutingModule" resourceType="Unspecified" preCondition="integratedMode" />
</handlers>
</system.webServer>

醒目(高亮)代码(Hiliting the code)

这是我们应用的和兴功能。我们将使用 http://hilite.me 提供的HTTP API 来完成高亮工作。开始消费这个API之情,先安装  Microsoft.Net.Http 包。

Install-Package Microsoft.Net.Http

这个类库提供简单的异步API用于发送HTTP请求和接收HTTP响应。所以,让我们用它来创建一个HTTP请求到hilite.me 服务。

// ~/Controllers/HomeController.cs

/* ... */

public class HomeController
{
/* ... */ private static async Task<string> HighlightSourceAsync(string source)
{
using (var client = new HttpClient())
{
var response = await client.PostAsync(
@"http://hilite.me/api",
new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "lexer", "c#" },
{ "style", "vs" },
{ "code", source }
})); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync();
}
} private static string HighlightSource(string source)
{
// Microsoft.Net.Http does not provide synchronous API,
// so we are using wrapper to perform a sync call.
return RunSync(() => HighlightSourceAsync(source));
} private static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
return Task.Run<Task<TResult>>(func).Unwrap().GetAwaiter().GetResult();
}
}

然后,在HomeController.Create 方法中调用它

   [HttpPost]
public ActionResult Create([Bind(Include = "SourceCode")] CodeSnippet snippet)
{
try
{
if (ModelState.IsValid)
{
snippet.CreatedAt = DateTime.UtcNow;
// We'll add the highlighting a bit later. //方案一:直接调用接口实现高亮
using (StackExchange.Profiling.MiniProfiler.StepStatic("Service call"))
{
snippet.HighlightedCode = HighlightSource(snippet.SourceCode);
snippet.HighlightedAt = DateTime.Now;
} HighLightRepository.Instance.Insert(snippet); return RedirectToAction("Details", new { id = snippet.Id });
} return View(snippet); }
catch (HttpRequestException)
{
ModelState.AddModelError("", "Highlighting service returned error. Try again later.");
}
return View(snippet);
}

Note

We are using synchronous controller action method, although it is recommended to use asynchronous one to make network calls inside ASP.NET request handling logic. As written in the given article, asynchronous actions greatly increase application capacity, but does not help to increase performance. You can test it by yourself with a sample application – there are no differences in using sync or async actions with a single request.

This sample is aimed to show you the problems related to application performance. And sync actions are used only to keep the tutorial simple. 不翻译,难。

问题

Tip

You can use the hosted sample to see what’s going on.

现在,运行这个程序,尝试创建一些代码片段,从一个小的开始。你有没有注意到一点延时,当你点击 Create 按钮后?

在我的开发机器上,它看起来用了0.5秒才重定向到详情页面。让我们看看MiniProfiler是什么导致这个延时:

我这里比他好点,用了1.3秒。

像我们看到的,调用web service是我们最大的问题。当我们尝试创建一个稍微大点的代码块时会发生什么?

.......省略两个截图.......

当我们扩大代码片段时,滞后也在增大。此外,考虑到语法高亮Web Service()经历着高负载,或者存在潜在的网络风险。或者考虑到这是CPU密集型任务,你也不能优化。

Moreover, consider that syntax highlighting web service (that is not under your control) experiences heavy load, or there are latency problems with network on their side. Or consider heavy CPU-intensive task instead of web service call that you can not optimize well.

Your users will be annoyed with un-responsive application and inadequate delays.

你的用户将对无响应的应用、严重的延时感到愤怒。

解决问题Solving a problem

面对这样的问题你能做什么? Async controller actions 没有什么帮助,像我前面earlier说的。你应该采用某种方法提取Web Service的调用和处理到HTTP请求之外,在后台运行。这里是一些方法:

  • Use recurring tasks and scan un-highlighted snippets on some interval.
  • Use job queues. Your application will enqueue a job, and some external worker threads will listen this queue for new jobs.

Ok, great. But there are several difficulties related to these techniques. The former requires us to set some check interval. Shorter interval can abuse our database, longer interval increases latency.

第一种是轮训没有高亮的片段,第二种是使用一个任务对立。

第一种轮训间隔小了数据库受不了,间隔打了用户受不了。

后一种方法解决了这个问题,但是带来了另外一个问题。队列是否可以持久化?你需要多少工作者线程?怎么协调他们?他们应当工作在哪,ASP.NET 应用内还是外,windows 服务?

最后一个问题是asp.net 应用处理长时间运行请求的痛点。

Warning

DO NOT run long-running processes inside of your ASP.NET application, unless they are prepared to die at any instruction and there is mechanism that can re-run them.

They will be simple aborted on application shutdown, and can be aborted even if the IRegisteredObject interface is used due to time out.

很多问题吧?Relax,你可以使用 Hangfire.它基于持久化队列解决应用程序重启,使用“可靠的获取”捕获意外线程终止,包含协调逻辑运行有多个工作者线程。并且可以足够简单的使用它。

Note

YOU CAN process your long-running jobs with Hangfire inside ASP.NET application – aborted jobs will be restarted automatically.

安装Hangfire (Installing Hangfire)

Install-Package Hangfire

安装完Hangfire,添加或更新OWIN Startup 类。在App_Start 文件夹下添加 Startup.cs 文件

[assembly: OwinStartup(typeof(Zeroes.Snippet.Highlight.App_Start.Startup))]

namespace Zeroes.Snippet.Highlight.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// 有关如何配置应用程序的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkID=316888
GlobalConfiguration.Configuration.UseSqlServerStorage("DefaultConnection"); app.UseHangfireDashboard();
app.UseHangfireServer();
}
}
}

当应用程序第一次启动时会自动创建所有的表。

移至后台(Moving to background)

首先,我们需要定义一个后台任务方法,当工作者线程捕获到高亮任务时调用。我们将简单的定义它为一个静态方法,在 HomeController 中,使用 snippetId  参数。

// ~/Controllers/HomeController.cs

/* ... Action methods ... */

// Process a job

// Process a job
public static void HighlightSnippet(int snippetId)
{

var snippet = HighLightRepository.Instance.GetById<CodeSnippet>(snippetId);
if (snippet == null) return;

snippet.HighlightedCode = HighlightSource(snippet.SourceCode);
snippet.HighlightedAt = DateTime.UtcNow;

HighLightRepository.Instance.Update(snippet);
}

 

注意这是一个简单的方法,不包含任何Hangfire相关的方法。他调用仓储实例获取一个代码片段,调用Web Service。(这里多线程抵用了HighLightRepository)

然后,我们需要一个地方调用这个方法加入队列。所以,让我们修改 Create 方法:

  [HttpPost]
public ActionResult Create([Bind(Include = "SourceCode")] CodeSnippet snippet)
{
try
{
if (ModelState.IsValid)
{
snippet.CreatedAt = DateTime.UtcNow;
//方案一:直接调用接口实现高亮
//using (StackExchange.Profiling.MiniProfiler.StepStatic("Service call"))
//{
// snippet.HighlightedCode = HighlightSource(snippet.SourceCode);
// snippet.HighlightedAt = DateTime.Now;
//} HighLightRepository.Instance.Insert(snippet);
//方案二:加入任务队列
using (StackExchange.Profiling.MiniProfiler.StepStatic("Job 队列"))
{
// Enqueue a job
BackgroundJob.Enqueue(() => HighlightSnippet(snippet.Id));
} return RedirectToAction("Details", new { id = snippet.Id });
} return View(snippet); }
catch (HttpRequestException)
{
ModelState.AddModelError("", "Highlighting service returned error. Try again later.");
}
return View(snippet);
}

终于快完了。尝试创建一些代码片段看看时间线吧。(不要担心你会看到空白页,我一会就处理他)

Good,1.3秒到0.27秒了。但是还存在另外一个问题。你是否注意到有时你重定向的页面内没有任何源码?发生这个是因为我们的视图里包含下面这行:

<div>@Html.Raw(Model.HighlightedCode)</div>

修改Details.cshtml

 @* ~/Views/Home/Details.cshtml *@

 @model   Zeroes.Snippet.Highlight.Models.CodeSnippet
@{ ViewBag.Title = "Details"; } <h2>Snippet <small>#@Model.Id</small></h2> <div>
<dl class="dl-horizontal">
<dt>@Html.DisplayNameFor(model => model.CreatedAt)</dt>
<dd>@Html.DisplayFor(model => model.CreatedAt)</dd>
<dt>@Html.DisplayNameFor(model => model.HighlightedAt)</dt>
<dd>@Html.DisplayFor(model => model.HighlightedAt)</dd>
</dl> <div class="clearfix"></div>
</div>
@*方案一*@
@*<div>@Html.Raw(Model.HighlightedCode)</div>*@ @*方案二*@
@if (Model.HighlightedCode == null)
{
<div class="alert alert-info">
<h4>Highlighted code is not available yet.</h4>
<p>
Don't worry, it will be highlighted even in case of a disaster
(if we implement failover strategies for our job storage).
</p>
<p>
<a href="javascript:window.location.reload()">Reload the page</a>
manually to ensure your code is highlighted.
</p>
</div> @Model.SourceCode
}
else
{
@Html.Raw(Model.HighlightedCode)
}

Details.cshtml

另外你需要拉取你的应用程序使用AJAX,直到它返回高亮代码。

         //获取高亮代码
public ActionResult HighlightedCode(int snippetId)
{
var snippet = HighLightRepository.Instance.GetById<CodeSnippet>(snippetId);
if (snippet.HighlightedCode == null)
{
return new HttpStatusCodeResult(HttpStatusCode.NoContent);
}
return Content(snippet.HighlightedCode);
}

HighlightedCode

Or you can also use send a command to users via SignalR channel from your HighlightSnippet method. But that’s another story.

Note

Please, note that user still waits until its source code will be highlighted. But the application itself became more responsive and he is able to do another things while background job is processed.

总结

In this tutorial you’ve seen that:

  • Sometimes you can’t avoid long-running methods in ASP.NET applications.
  • Long running methods can cause your application to be un-responsible from the users point of view.
  • To remove waits you should place your long-running method invocation into background job.
  • Background job processing is complex itself, but simple with Hangfire.
  • You can process background jobs even inside ASP.NET applications with Hangfire.

翻译、贴代码用了快3个小时,现在都快吐了。如果您觉着有点用处,就点个赞吧、赞吧、赞吧!

希望更多的人一起学习这个组件,下载地址(稍后补上,CSDN上传太慢了)

补两个界面:

[翻译+山寨]Hangfire Highlighter Tutorial的更多相关文章

  1. Hangfire Highlighter Tutorial

    Hangfire Highlighter Tutorial Hangfire是一个开源且商业免费使用的工具函数库.可以让你非常容易地在ASP.NET应用(也可以不在ASP.NET应用)中执行多种类型的 ...

  2. hangfire+bootstrap ace 模板实现后台任务管理平台

    前言 前端时间刚开始接触Hangfire就翻译了一篇官方的教程[翻译+山寨]Hangfire Highlighter Tutorial,后来在工作中需要实现一个异步和定时执行的任务管理平台,就结合bo ...

  3. 自定义 URL Scheme 完全指南

    本文由 Migrant 翻译自 The Complete Tutorial on iOS/iPhone Custom URL Schemes,转载请注明出处. 注意: 自从自定义 URL 的引入,本文 ...

  4. Python资源大全

    The Python Tutorial (Python 2.7.11) 的中文翻译版本.Python Tutorial 为初学 Python 必备官方教程,本教程适用于 Python 2.7.X 系列 ...

  5. AngularJS 中文资料+工具+库+Demo 大搜集

    中文学习资料: 中文资料且成系统的就这么多,优酷上有个中文视频. http://www.cnblogs.com/lcllao/archive/2012/10/18/2728787.html   翻译的 ...

  6. ReactiveCocoa入门教程——第一部分

      ReactiveCocoa iOS 翻译    2015-01-22 02:33:37    11471    6    15 本文翻译自RayWenderlich  ReactiveCocoa ...

  7. Caffe2 Tutorials[0]

    本系列教程包括9个小节,对应Caffe2官网的前9个教程,第10个教程讲的是在安卓下用SqueezeNet进行物体检测,此处不再翻译.另外由于栏主不关注RNN和LSTM,所以栏主不对剩下两个教程翻译. ...

  8. Redis集群学习笔记

    Redis集群学习笔记 前言 最近有个需求,就是将一个Redis集群中数据转移到某个单机Redis上. 迁移Redis数据的话,如果是单机Redis,有两种方式: a. 执行redis-cli shu ...

  9. phython学习

    Python 中文学习大本营 关于作者 赞助本站 The Python Tutorial (Python 2.7.X) 的中文翻译版本.Python Tutorial 为初学 Python 必备官方教 ...

随机推荐

  1. zookeeper(单机/集群)安装与配置

    一.安装与单机配置 1.下载: wget http://archive.apache.org/dist/zookeeper/stable/zookeeper-3.4.6.tar.gz 如果网站下载不了 ...

  2. Jquery EasyUI 开发实录

    有好几年没有用过EasyUI了,最近在外包做的一个项目中新增功能时,又用到了,本以为和按照以前那样用就可以了,可当我真正用的时候,发现许多地方不一样了,就连官网的文档都更新了,最突出的就是不知道什么时 ...

  3. Jexus 服务器部署导航

    说明:本索引只是方便本人查找,不涉及版权问题,所有博客,还是到元博客地址访问. <script async src="//pagead2.googlesyndication.com/p ...

  4. 一步步开发自己的博客 .NET版(3、注册登录功能)

    前言 这次开发的博客主要功能或特点:    第一:可以兼容各终端,特别是手机端.    第二:到时会用到大量html5,炫啊.    第三:导入博客园的精华文章,并做分类.(不要封我)    第四:做 ...

  5. BIO\NIO\AIO记录

    IO操作可以分为3类:同步阻塞(BIO).同步非阻塞(NIO).异步(AIO). 同步阻塞(BIO):在此种方式下,用户线程发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后, ...

  6. ENode 2.8 最新架构图简介

    ENode架构图 什么是ENode ENode是一个.NET平台下,纯C#开发的,基于DDD,CQRS,ES,EDA,In-Memory架构风格的,可以帮助开发者开发高并发.高吞吐.可伸缩.可扩展的应 ...

  7. RavenDB官网文档翻译系列第一

    本系列文章主要翻译自RavenDB官方文档,有些地方做了删减,有些内容整合在一起.欢迎有需要的朋友阅读.毕竟还是中文读起来更亲切吗.下面进入正题. 起航 获取RavenDB RavenDB可以通过Nu ...

  8. 如何使用yum 下载 一个 package ?如何使用 yum install package 但是保留 rpm 格式的 package ? 或者又 如何通过yum 中已经安装的package 导出它,即yum导出rpm?

    注意 RHEL5 和 RHEL6 的不同 How to use yum to download a package without installing it Solution Verified - ...

  9. hibernate一对一主键单向关联

    关联是类(类的实例)之间的关系,表示有意义和值得关注的连接. 本系列将介绍Hibernate中主要的几种关联映射 Hibernate一对一主键单向关联Hibernate一对一主键双向关联Hiberna ...

  10. 学习日记-从爬虫到接口到APP

    最近都在复习J2E,多学习一些东西肯定是好的,而且现在移动开发工作都不好找了,有工作就推荐一下小弟呗,广州佛山地区,谢谢了. 这篇博客要做的效果很简单,就是把我博客的第一页每个条目显示在APP上,条目 ...