首页 > 编程语言 >HowToDoInJava-其它教程-1-一-

HowToDoInJava-其它教程-1-一-

时间:2024-10-24 18:11:21浏览次数:1  
标签:教程 其它 测试 class HowToDoInJava import org public 注解

HowToDoInJava 其它教程 1(一)

原文:HowToDoInJava

协议:CC BY-NC-SA 4.0

Maven 本地仓库位置以及如何更改?

原文: https://howtodoinjava.com/maven/change-local-repository-location/

在本教程中,学习更改 Maven 本地仓库的位置。 Maven 是构建和依赖项管理工具。 它将所需的项目依赖项下载到我们的本地系统,并将其包含到定义的项目编译或运行时中。

1. Maven 的默认本地仓库位置

默认情况下,maven 的本地仓库位于${user.home}/.m2/repository上。 在不同的操作系统中,这些路径可以解决:

Windows 7: 		C:/Documents and Settings/<username>/.m2/repository
Windows 10:		C:/Users/<username>/.m2/repository
Linux: 			/home/<username>/.m2/repository
Mac: 			/Users/<username>/.m2/repository

我们可以将本地仓库位置更改为我们选择的其他位置。

2. 更改 Maven 本地仓库位置

Maven 作为存档文件夹分发。 通常,开发人员下载 Maven 并将其解压缩到他们的工作站。

下载了 Maven 之后,请按照给定的简单步骤将 Maven 本地仓库位置更改为其他路径。

  • 导航到路径{M2_HOME}/conf/,其中M2_HOME是 maven 安装文件夹。

  • 在某些文本编辑器中以编辑方式打开文件settings.xml

  • 细化标签<localRepository>

  • 更新此标记值中的所需路径。 保存文件。

    	<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
    
    	  <!-- localRepository
    	   | The path to the local repository maven will use to store artifacts.
    	   |
    	   | Default: ${user.home}/.m2/repository -->
    
    	  <localRepository>E:/devsetup/M2</localRepository>
    
    	  ...
    	  ...
    
     	</settings>
    
    
  • 恭喜,您完成了。

    Maven local repository path

    Maven 本地仓库路径

学习愉快!

Jersey 使用过滤器记录请求和响应实体

原文: https://howtodoinjava.com/jersey/jersey-custom-logging-request-and-response-entities-using-filter/

默认情况下,Jersey 使用 JUL 进行日志记录 - 不会在日志中打印请求/响应实体主体。 要打印实体内容,必须创建自己的LoggingFiler,并将其注册为默认值org.glassfish.jersey.filter.LoggingFilter。 在此示例中,我正在创建一个这样的基本CustomLoggingFilter,它扩展了org.glassfish.jersey.filter.LoggingFilter并实现了ContainerRequestFilterContainerResponseFilter接口,以便它可以拦截正在进行的请求和响应。

Table of Contents

Create CustomLoggingFilter
Register CustomLoggingFilter
Log statements with default LoggingFilter 
Log statements with CustomLoggingFilter

创建CustomLoggingFilter

让我们直接转到此 Jersey 演示的自定义日志记录过滤器类。

package com.howtodoinjava.jersey.provider;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;

import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.message.internal.ReaderWriter;

public class CustomLoggingFilter extends LoggingFilter implements ContainerRequestFilter, ContainerResponseFilter 
{
	@Override
	public void filter(ContainerRequestContext requestContext)	throws IOException 
	{
		StringBuilder sb = new StringBuilder();
		sb.append("User: ").append(requestContext.getSecurityContext().getUserPrincipal() == null ? "unknown"
						: requestContext.getSecurityContext().getUserPrincipal());
		sb.append(" - Path: ").append(requestContext.getUriInfo().getPath());
		sb.append(" - Header: ").append(requestContext.getHeaders());
		sb.append(" - Entity: ").append(getEntityBody(requestContext));
		System.out.println("HTTP REQUEST : " + sb.toString());
	}

	private String getEntityBody(ContainerRequestContext requestContext) 
	{
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		InputStream in = requestContext.getEntityStream();

		final StringBuilder b = new StringBuilder();
		try 
		{
			ReaderWriter.writeTo(in, out);

			byte[] requestEntity = out.toByteArray();
			if (requestEntity.length == 0)
			{
				b.append("").append("\n");
			}
			else
			{
				b.append(new String(requestEntity)).append("\n");
			}
			requestContext.setEntityStream( new ByteArrayInputStream(requestEntity) );

		} catch (IOException ex) {
			//Handle logging error
		}
		return b.toString();
	}

	@Override
	public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException 
	{
		StringBuilder sb = new StringBuilder();
		sb.append("Header: ").append(responseContext.getHeaders());
		sb.append(" - Entity: ").append(responseContext.getEntity());
		System.out.println("HTTP RESPONSE : " + sb.toString());
	}
}

注册CustomLoggingFilter

要注册此CustomLoggingFilter,请以这种方式注册。

package com.howtodoinjava.jersey;

import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;

import com.howtodoinjava.jersey.provider.CustomLoggingFilter;

public class CustomApplication extends ResourceConfig 
{
	public CustomApplication() 
	{
		packages("com.howtodoinjava.jersey");
		register(JacksonFeature.class);

		register(CustomLoggingFilter.class);
	}
}

使用默认LoggingFilter的日志语句

现在,如果您尝试使用不带CustomLoggingFilter的任何现有 REST API,日志将以这种方式显示。

请求

Jersey-custom-logging

日志

Sep 30, 2015 6:18:41 PM org.glassfish.jersey.filter.LoggingFilter log
INFO: 1 * Server has received a request on thread http-bio-8080-exec-4
1 > POST http://localhost:8080/JerseyDemos/rest/employees
1 > accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
1 > accept-encoding: gzip, deflate
1 > accept-language: null
1 > cache-control: no-cache
1 > connection: keep-alive
1 > content-length: 35
1 > content-type: application/json; charset=UTF-8
1 > host: localhost:8080
1 > pragma: no-cache
1 > user-agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0

Sep 30, 2015 6:18:41 PM org.glassfish.jersey.filter.LoggingFilter log
INFO: 1 * Server responded with a response on thread http-bio-8080-exec-4
1 < 200
1 < Content-Type: application/json

使用CustomLoggingFilter的日志语句

添加CustomLoggingFilter后,您将获得更好的日志,如下所示。

HTTP REQUEST : User: unknown - Path: employees - Header: {host=[localhost:8080], user-agent=[Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0], accept=, accept-language=[null], accept-encoding=[gzip, deflate], content-type=[application/json; charset=UTF-8], content-length=[35], connection=[keep-alive], pragma=[no-cache], cache-control=[no-cache]} - Entity: {"id":2,"name":"Alex Kolenchiskey"}

HTTP RESPONSE : Header: {Content-Type=[application/json]} - Entity: Employee [id=2, name=Alex Kolenchiskey]

请根据需要随时从日志语句中添加/删除信息。 您可以在这些日志中添加许多其他有用的信息。

祝您学习愉快!

Jersey - 如何在 REST API 响应中设置 Cookie

原文: https://howtodoinjava.com/jersey/jersey-how-to-set-cookie-in-rest-api-response/

在此示例中,我们将学习为 Jersey REST API 发送的 HTTP 响应设置 cookie。 本示例利用javax.ws.rs.core.Response将 Cookie 设置为发送到 REST 客户端的 REST 响应中。

要在 REST API 响应中设置 Cookie,请获取Response参考并使用其cookie()方法。

Response.ok().entity(list).cookie(new NewCookie("cookieResponse", "cookieValueInReturn")).build();

Rest API 示例代码

我在下面编写了 REST API 进行测试。

@GET
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response getAllEployees() 
{
	Employees list = new Employees();
	list.setEmployeeList(new ArrayList<Employee>());

	list.getEmployeeList().add(new Employee(1, "Lokesh Gupta"));
	list.getEmployeeList().add(new Employee(2, "Alex Kolenchiskey"));
	list.getEmployeeList().add(new Employee(3, "David Kameron"));

	return Response.ok().entity(list).cookie(new NewCookie("cookieResponse", "cookieValueInReturn")).build();
}

演示

现在,使用 Jersey 客户端代码在 REST API 之上进行调用。

public static void main(String[] args) 
{
	Client client = ClientBuilder.newClient( new ClientConfig().register( LoggingFilter.class ) );
	WebTarget webTarget = client.target("http://localhost:8080/JerseyDemos/rest").path("employees");

	Invocation.Builder invocationBuilder =  webTarget.request(MediaType.APPLICATION_JSON);
	Response response = invocationBuilder.get();

	Employees employees = response.readEntity(Employees.class);
	List<Employee> listOfEmployees = employees.getEmployeeList();

	System.out.println(response.getCookies());
	System.out.println(response.getStatus());
	System.out.println(Arrays.toString( listOfEmployees.toArray(new Employee[listOfEmployees.size()]) ));
}

Output: 

{cookieResponse=cookieResponse=cookieValueInReturn;Version=1}
200
[Employee [id=1, name=Lokesh Gupta], Employee [id=2, name=Alex Kolenchiskey], Employee [id=3, name=David Kameron]]

祝您学习愉快!

Jersey 文件下载示例 – StreamingOutput

原文: https://howtodoinjava.com/jersey/jersey-streamingoutput/

在此 Jersey 文件下载示例中,我们将学习编写一个 Jersey rest api ,该 API 可以流式传输或下载文件(例如 PDF/Excel/Text 文件)发送给请求的客户端。 我将使用javax.ws.rs.core.StreamingOutput类来构建此 JAX-RS API。

Table of Contents

1\. REST API to stream file with StreamingOutput
2\. Jersey file download demo
3\. Maven dependencies
4\. web.xml changes

1. 使用StreamingOutput传输文件的 REST API

以下是使用 JAX-RS Jersey 的StreamingOutput编写流式 REST API 的源代码。

package com.howtodoinjava.jersey;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;

@Path("/download")
public class JerseyService 
{
	@GET
	@Path("/pdf")
	public Response downloadPdfFile()
	{
		StreamingOutput fileStream =  new StreamingOutput() 
		{
			@Override
			public void write(java.io.OutputStream output) throws IOException, WebApplicationException 
			{
				try 
				{
					java.nio.file.Path path = Paths.get("C:/temp/test.pdf");
					byte[] data = Files.readAllBytes(path);
					output.write(data);
					output.flush();
				} 
				catch (Exception e) 
				{
					throw new WebApplicationException("File Not Found !!");
				}
			}
		};
		return Response
	            .ok(fileStream, MediaType.APPLICATION_OCTET_STREAM)
	            .header("content-disposition","attachment; filename = myfile.pdf")
	            .build();
	}
}

2. Jersey 文件下载演示

如果点击 URL “http://localhost:8080/JerseyDemos/rest/download/pdf”,则会在浏览器中显示以下警告,以下载文件。 PDF 文件将被保存的filename将是您在Response.header()方法中设置的。

Jersey file download example

Jersey 文件下载示例

请确保您在 API 代码中指定的路径中存在文件,否则会出错。

3. Maven 依赖

为了快速参考,请参阅下面我用于此演示的 maven 文件。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd;
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.howtodoinjava.jersey</groupId>
	<artifactId>JerseyDemos</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<repositories>
		<repository>
			<id>maven2-repository.java.net</id>
			<name>Java.net Repository for Maven</name>
			<url>http://download.java.net/maven/2/</url>
			<layout>default</layout>
		</repository>
	</repositories>
	<properties>
		<jersey2.version>2.19</jersey2.version>
		<jaxrs.version>2.0.1</jaxrs.version>
	</properties>
	<dependencies>
		<!-- JAX-RS -->
		<dependency>
			<groupId>javax.ws.rs</groupId>
			<artifactId>javax.ws.rs-api</artifactId>
			<version>${jaxrs.version}</version>
		</dependency>
		<!-- Jersey2.19 -->
		<dependency>
			<groupId>org.glassfish.jersey.containers</groupId>
			<artifactId>jersey-container-servlet</artifactId>
			<version>${jersey2.version}</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.jersey.core</groupId>
			<artifactId>jersey-server</artifactId>
			<version>${jersey2.version}</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.jersey.core</groupId>
			<artifactId>jersey-client</artifactId>
			<version>${jersey2.version}</version>
		</dependency>
	</dependencies>
	<build>
		<finalName>JerseyDemos</finalName>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

4. web.xml的更改

另外,如果您正在使用Jersey2 配置,请参考web.xml文件。

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>

  <display-name>Archetype Created Web Application</display-name>

  <servlet>
        <servlet-name>jersey-serlvet</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
             <param-name>jersey.config.server.provider.packages</param-name>
             <param-value>com.howtodoinjava.jersey</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>jersey-serlvet</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>

</web-app>

源码下载

在下面的评论部分中,将有关 Java 流文件下载示例的问题交给我。

学习愉快!

Jersey 文件上传示例 – Jersey2 MultiPartFeature

原文: https://howtodoinjava.com/jersey/jersey-file-upload-example/

在此 Jersey2 文件上传示例中,我们将学习如何使用 Jersey 的多部分表单数据支持上传二进制文件(例如本示例中的 PDF 文件)。 我们将在下面学习完成功能所需的更改。

Table of Contents

1\. Jersey maven multipart dependency
2\. Add MultiPartFeature in web.xml
3\. Write Jersey Upload REST API
4\. Test file upload using HTML Form
5\. Test file upload using jersey client

1. Jersey Maven 多部分依赖

要使用多部分功能,您需要将jersey-media-multipart模块添加到pom.xml文件中:

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-multipart</artifactId>
    <version>2.19</version>
</dependency>

2. 在web.xml中添加 Jersey MultiPartFeature

此外,您还需要在 Jersey 配置中添加MultiPartFeature,以使其知道您将使用多部分请求。 最简单的方法是通过web.xml文件添加支持。

<servlet>
	<servlet-name>jersey-serlvet</servlet-name>
	<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
	<init-param>
		<param-name>jersey.config.server.provider.packages</param-name>
		<param-value>com.howtodoinjava.jersey</param-value>
	</init-param>
	<init-param>
		<param-name>jersey.config.server.provider.classnames</param-name>
		<param-value>org.glassfish.jersey.media.multipart.MultiPartFeature</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>

3. 编写 Jersey 文件上传 REST API

现在查看用于文件上传的实际 REST API,它将接收并保存文件。

@POST
@Path("/pdf")
@Consumes({MediaType.MULTIPART_FORM_DATA})
public Response uploadPdfFile(	@FormDataParam("file") InputStream fileInputStream,
        						@FormDataParam("file") FormDataContentDisposition fileMetaData) throws Exception
{
	String UPLOAD_PATH = "c:/temp/";
	try 
	{
		int read = 0;
		byte[] bytes = new byte[1024];

		OutputStream out = new FileOutputStream(new File(UPLOAD_PATH + fileMetaData.getFileName()));
		while ((read = fileInputStream.read(bytes)) != -1) 
		{
			out.write(bytes, 0, read);
		}
		out.flush();
		out.close();
	} catch (IOException e) 
	{
		throw new WebApplicationException("Error while uploading file. Please try again !!");
	}
	return Response.ok("Data uploaded successfully !!").build();
}

5. 使用 HTML 表单测试文件上传

只需在“webapp”文件夹中创建文件“fileUpload.html”文件,然后粘贴此代码即可。

<html>
	<body>
		<h1>File Upload Example - howtodoinjava.com</h1>

		<form action="rest/upload/pdf" method="post" enctype="multipart/form-data">

			<p>Select a file : <input type="file" name="file" size="45" accept=".pdf" /></p>
			<input type="submit" value="Upload PDF" />

		</form>

	</body>
</html>

现在点击 URL:“http://localhost:8080/JerseyDemos/fileUpload.html”,它将显示一个 HTML 文件控件来浏览文件。 选择任何 PDF 文件,然后单击“Upload PDF”按钮。

您的文件将被上传,并且您将收到消息:“数据成功上传!”

5. 使用 jersey 客户端测试文件上传

如果要使用 Java 客户端上传文件,则可以根据需要修改以下工作代码。

使用FormDataMultiPart示例上传 Jersey 文件。

public static void main(String[] args) throws IOException 
{
	final Client client = ClientBuilder.newBuilder().register(MultiPartFeature.class).build();

	final FileDataBodyPart filePart = new FileDataBodyPart("file", new File("C:/temp/sample.pdf"));
	FormDataMultiPart formDataMultiPart = new FormDataMultiPart();
	final FormDataMultiPart multipart = (FormDataMultiPart) formDataMultiPart.field("foo", "bar").bodyPart(filePart);

	final WebTarget target = client.target("http://localhost:8080/JerseyDemos/rest/upload/pdf");
	final Response response = target.request().post(Entity.entity(multipart, multipart.getMediaType()));

	//Use response object to verify upload success

	formDataMultiPart.close();
	multipart.close();
}

Jersey 文件上传示例源码

在评论框中输入您的与 Jersey2 多部分文件上传示例相关的问题。

学习愉快!

参考: https://jersey.java.net/documentation/latest/user-guide.html#multipart

Jersey - Ajax 多文件上传示例

原文: https://howtodoinjava.com/jersey/jax-rs-jersey-ajax-multi-file-upload-example/

了解如何结合使用 Ajax 和 JAX-RS 网络服务(示例中使用 Jersey)一次性上传多个文件。 还要看看基于表单的文件上传示例文件下载示例

Jersey multi-file upload success

Table of Contents

Jersey maven multipart dependency
Add MultiPartFeature
Write Upload REST API
HTML/Ajax code
Demo of Multiple files upload

Jersey Maven 多部分依赖

要使用多部分功能,您需要将jersey-media-multipart模块以及其他必需的模块添加到pom.xml文件中。

<project xmlns="http://maven.apache.org/POM/4.0.0" 
		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
		 http://maven.apache.org/xsd/maven-4.0.0.xsd;
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.howtodoinjava.jersey</groupId>
	<artifactId>JerseyDemos</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<repositories>
		<repository>
			<id>maven2-repository.java.net</id>
			<name>Java.net Repository for Maven</name>
			<url>http://download.java.net/maven/2/</url>
			<layout>default</layout>
		</repository>
	</repositories>
	<properties>
		<jersey2.version>2.19</jersey2.version>
		<jaxrs.version>2.0.1</jaxrs.version>
	</properties>
	<dependencies>
		<!-- JAX-RS -->
		<dependency>
			<groupId>javax.ws.rs</groupId>
			<artifactId>javax.ws.rs-api</artifactId>
			<version>${jaxrs.version}</version>
		</dependency>
		<!-- Jersey2.19 -->
		<dependency>
			<groupId>org.glassfish.jersey.containers</groupId>
			<artifactId>jersey-container-servlet</artifactId>
			<version>${jersey2.version}</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.jersey.core</groupId>
			<artifactId>jersey-server</artifactId>
			<version>${jersey2.version}</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.jersey.core</groupId>
			<artifactId>jersey-client</artifactId>
			<version>${jersey2.version}</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.jersey.media</groupId>
			<artifactId>jersey-media-multipart</artifactId>
			<version>${jersey2.version}</version>
		</dependency>
	</dependencies>
	<build>
		<finalName>JerseyDemos</finalName>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

添加MultiPartFeature

此外,还需要在 Jersey 配置中添加MultiPartFeature,以使其知道您将使用多部分请求。 最简单的方法是通过web.xml文件添加支持。

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>

	<display-name>Archetype Created Web Application</display-name>

	<servlet>
		<servlet-name>jersey-serlvet</servlet-name>
		<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
		<init-param>
			<param-name>jersey.config.server.provider.packages</param-name>
			<param-value>com.howtodoinjava.jersey</param-value>
		</init-param>
		<init-param>
			<param-name>jersey.config.server.provider.classnames</param-name>
			<param-value>org.glassfish.jersey.filter.LoggingFilter;
						 org.glassfish.jersey.media.multipart.MultiPartFeature</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>jersey-serlvet</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>

</web-app>

编写上传 REST API

现在编写服务层 JAX-RS API,它实际上将具有将上传的文件存储在服务器上的逻辑。

package com.howtodoinjava.jersey;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;

@Path("/upload")
public class JerseyService 
{
	@POST
	@Path("/pdf")
	@Consumes({MediaType.MULTIPART_FORM_DATA})
	public Response uploadPdfFile(	@FormDataParam("file") InputStream fileInputStream,
            						@FormDataParam("file") FormDataContentDisposition fileMetaData) throws Exception
	{
		String UPLOAD_PATH = "c:/temp/";
		try 
		{
			int read = 0;
			byte[] bytes = new byte[1024];

			OutputStream out = new FileOutputStream(new File(UPLOAD_PATH + fileMetaData.getFileName()));
			while ((read = fileInputStream.read(bytes)) != -1) 
			{
				out.write(bytes, 0, read);
			}
			out.flush();
			out.close();
		} catch (IOException e) 
		{
			throw new WebApplicationException("Error while uploading file. Please try again !!");
		}
		return Response.ok(fileMetaData.getFileName() + " uploaded successfully !!").build();
	}
}

HTML/Ajax 代码

是时候看看客户端代码了。 我编写了一个功能非常简单的非常简单的文件。 如果您要对其进行某些特定的更改,请告诉我。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
	<head>
		<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
		<meta content="utf-8" http-equiv="encoding" />

		<script src="./js/jquery.min.js"></script>
		<script type="text/javascript">

		$(document).ready(function() 
		{
			$("#uploadBtn").click(function() 
			{
				$('input[name="file"]').each(function(index, value) 
				{ 
				    var file = value.files[0];
				    if(file) 
				    {
				    	var formData = new FormData();
						formData.append('file', file);
						$.ajax({
						  url : '/JerseyDemos/rest/upload/pdf',
						  type : 'POST',
						  data : formData,
						  cache : false,
						  contentType : false,
						  processData : false,
						  success : function(data, textStatus, jqXHR) {
						    	var message = jqXHR.responseText;
						    	$("#messages").append("<li>" + message + "</li>");
						  },
						  error : function(jqXHR, textStatus, errorThrown) {
							  	$("#messages").append("<li style='color: red;'>" + textStatus + "</li>");
						  }
						});
					}
				});
			});
		});
		</script>
</head>
<body>
	<h1>JAX-RS Multi-file Upload Example</h1>

	<form action="rest/upload/pdf" method="post" enctype="multipart/form-data">

		<p>
			Select file 1: <input type="file" name="file" size="45"	accept=".pdf" />
		</p>
		<p>
			Select file 2: <input type="file" name="file" size="45"	accept=".pdf" />
		</p>
		<p>
			Select file 3: <input type="file" name="file" size="45"	accept=".pdf" />
		</p>
		<p>
			<input id="uploadBtn" type="button" value="Upload PFD Files" />
		</p>

	</form>

	<ul id="messages">	
	</ul>

</body>
</html>

多个文件上传演示

演示时间。 在服务器上部署应用,然后单击 URL:http://localhost:8080/JerseyDemos/fileUpload.html

Jersey multifile upload example - blank form

Jersey 多文件上传示例 – 空白表格

现在选择两个/三个文件,然后单击上载按钮。 您将在屏幕上看到以下消息。 文件也将存储在服务器上。

Jersey multi-file upload success

Jersey 多文件上传成功

在评论框中让我知道您的问题。

祝您学习愉快!

Jersey 异常处理 – Jersey ExceptionMapper示例

原文: https://howtodoinjava.com/jersey/jaxrs-jersey-exceptionmapper/

在 Jersey ExceptionMapper示例中,我们将学习在开发 Jersey RESTful Web 服务时使用ExceptionMapper接口处理自定义异常。 出于演示目的,我正在修改为Jersey 下载文件示例编写的源代码。

Table Of Contents

1\. Jersey custom exception with ExceptionMapper
2\. How to throw exception from REST API
3\. Demo

1. Jersey ExceptionMapper – 创建自定义异常

要在基于 JAX-RS 的 Web 服务中处理自定义异常,您应该创建一个异常类,然后实现ExceptionMapper接口。

package com.howtodoinjava.jersey;

import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class MissingFileException extends Exception implements
				ExceptionMapper<MissingFileException> 
{
	private static final long serialVersionUID = 1L;

	public MissingFileException() {
		super("No File found with given name !!");
	}

	public MissingFileException(String string) {
		super(string);
	}

	@Override
	public Response toResponse(MissingFileException exception) 
	{
		return Response.status(404).entity(exception.getMessage())
									.type("text/plain").build();
	}
}

2. 如何从 REST API 引发异常

现在,如果在所需位置找不到用户请求的文件,则可以抛出MissingFileException

package com.howtodoinjava.jersey;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;

@Path("/download")
public class JerseyService 
{
	@GET
	@Path("/{fileName}")
	public Response downloadPdfFile(final @PathParam("fileName") String fileName) throws MissingFileException
	{
		final String fullFilePath = "C:/temp/" + fileName;

		File file = new File(fullFilePath);

		if(file.exists() == false){
			throw new MissingFileException(fileName + " does not existing on this server !!");
		}

		StreamingOutput fileStream =  new StreamingOutput()
		{
			@Override
			public void write(java.io.OutputStream output) throws IOException
			{
				try 
				{
					java.nio.file.Path path = Paths.get(fullFilePath);
					byte[] data = Files.readAllBytes(path);
					output.write(data);
					output.flush();
				} 
				catch (IOException e) 
				{
					throw new IOException("Error while reading file :: '"+fileName+"' !!");
				}
			}
		};
		return Response
	            .ok(fileStream, MediaType.APPLICATION_OCTET_STREAM)
	            .header("content-disposition","attachment; filename = '"+fileName)
	            .build();
	}
}

3. Jersey 异常处理示例

现在该测试 Jersey 异常映射器了。 现在,让我们看看找不到文件时会发生什么。

3.1 当用户要求正确的文件时

No exception when file is found

找到文件时没有异常

3.2 当用户要求提供未知文件时

404 with custom message when file is not found

找不到文件时带有自定义消息的 404

3.3 未捕获的异常处理

如果要在进入用户屏幕之前处理所有未捕获的异常,则必须映射Throwable本身。

package com.howtodoinjava.jersey.provider;

import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class UncaughtException extends Throwable implements ExceptionMapper<Throwable>
{
    private static final long serialVersionUID = 1L;

    @Override
    public Response toResponse(Throwable exception)
    {
        return Response.status(500).entity("Something bad happened. Please try again !!").type("text/plain").build();
    }
}

请问您有关 jaxrs 异常映射器示例的问题。

学习愉快!

Jersey + MOXy JSON 示例

原文: https://howtodoinjava.com/jersey/jax-rs-jersey-moxy-json-example/

本教程说明如何在 Jersey2.x 中使用 MOXy JSON 功能。 MOXy 是 Jersey2.x 中的默认 JSON 绑定供应器。 尽管由于性能原因,我个人还是更喜欢 Jackson,而不是 MOXy。

Table of Contents

MOXy maven dependencies/changes
REST API code
Model bean changes
Manually adding MoxyJsonFeature
Customize behavior using MoxyJsonConfig
Demo

MOXy Maven 依赖项/更改

MOXy 媒体模块是 Jersey2.x 中的模块之一,您无需在其中明确注册其功能,例如MoxyJsonFeature。 一旦 Jersey 检测到添加了它的存在,它就会自动注册。 因此,只需在pom.xml中添加 MOXy 依赖项就可以完成一半的工作。

<dependency>
	<groupId>org.glassfish.jersey.media</groupId>
	<artifactId>jersey-media-moxy</artifactId>
	<version>2.19</version>
</dependency>

REST API 代码

在编写 API 的服务端,您需要使用@Produces(MediaType.APPLICATION_JSON)注解启用 JSON 媒体类型。

JerseyService.java

@Path("/employees")
public class JerseyService 
{
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public Employees getAllEmployees() 
	{
		Employees list = new Employees();
		list.setEmployeeList(new ArrayList<Employee>());

		list.getEmployeeList().add(new Employee(1, "Lokesh Gupta"));
		list.getEmployeeList().add(new Employee(2, "Alex Kolenchiskey"));
		list.getEmployeeList().add(new Employee(3, "David Kameron"));

		return list;
	}
}	

模型 bean 更改

在模型 bean 方面,您无需放置任何注解或任何配置。 默认情况下它将起作用。 您甚至也不需要添加任何根注解。

Employees.java

public class Employees 
{
	private List<Employee> employeeList;

	public List<Employee> getEmployeeList() {
		return employeeList;
	}

	public void setEmployeeList(List<Employee> employeeList) {
		this.employeeList = employeeList;
	}
}

Employee.java

public class Employee 
{
	private Integer id;
	private String name;

	public Employee() {

	}

	public Employee(Integer id, String name) {
		this.id  = id;
		this.name = name;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + "]";
	}
}

手动添加MoxyJsonFeature

尽管MoxyJsonFeature是自动注册的,但是如果您希望手动注册,则可以按以下配置添加它。

public class CustomApplication extends Application
{
	//Add Service APIs
	@Override
	public Set<Class<?>> getClasses() 
	{
		Set<Class<?>> resources = new HashSet<Class<?>>();

		//register REST modules
		resources.add(JerseyService.class);

		//Manually adding MOXyJSONFeature
		resources.add(org.glassfish.jersey.moxy.json.MoxyJsonFeature.class);

		//Configure Moxy behavior
		resources.add(JsonMoxyConfigurationContextResolver.class);

		return resources;
	}
}

并将此Application类添加到web.xml文件中。

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>

	<display-name>Archetype Created Web Application</display-name>

	<servlet>
		<servlet-name>jersey-serlvet</servlet-name>
		<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
		<init-param>
			<param-name>javax.ws.rs.Application</param-name>
			<param-value>com.howtodoinjava.jersey.CustomApplication</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>jersey-serlvet</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>

</web-app>

使用MoxyJsonConfig自定义行为

MOXy 在 JAXB 之上提供了某些功能,您可以通过提供MoxyJsonConfig实现来启用/禁用这些功能。

//Register ContextResolver<MoxyJsonConfig> to override 
//default behavior of marshaling/un-marshaling attributes

@Provider
public class JsonMoxyConfigurationContextResolver implements ContextResolver<MoxyJsonConfig> 
{

	private final MoxyJsonConfig config;

	public JsonMoxyConfigurationContextResolver() 
	{
		final Map<String, String> namespacePrefixMapper = new HashMap<String, String>();
		namespacePrefixMapper.put("http://www.w3.org/2001/XMLSchema-instance", "xsi");

		config = new MoxyJsonConfig()
				.setNamespacePrefixMapper(namespacePrefixMapper)
				.setNamespaceSeparator(':')
				.setAttributePrefix("")
				.setValueWrapper("value")
				.property(JAXBContextProperties.JSON_WRAPPER_AS_ARRAY_NAME, true)
				.setFormattedOutput(true)
				.setIncludeRoot(true)
				.setMarshalEmptyCollections(true);
	}

	@Override
	public MoxyJsonConfig getContext(Class<?> objectType) 
	{
		return config;
	}
}

JsonMoxyConfigurationContextResolver已被添加到上面的CustomApplication类中的行号 16。

演示

只需将您的应用部署在任何服务器上,然后单击 URL:http://localhost:8080/JerseyDemos/rest/employees

这将产生以下输出。

{
  "employeeList": [
    {
      "id": 1,
      "name": "Lokesh Gupta"
    },
    {
      "id": 2,
      "name": "Alex Kolenchiskey"
    },
    {
      "id": 3,
      "name": "David Kameron"
    }
  ]
}

以下是演示应用中上述文件的层次结构。

Jersey2.x MOXy JSON Demo Application Structure

Jersey2.x MOXy JSON 示例应用结构

在下面将您的问题和评论发送给我。

祝您学习愉快!

参考https://jersey.java.net/documentation/latest/media.html#json.moxy

Jersey + JSONP 示例

原文: https://howtodoinjava.com/jersey/jax-rs-jersey-jsonp-example/

本教程介绍了如何在 Jersey2.x 中使用 JSONP JSON 供应器。 就像我们在 Jersey MOXy 示例中讨论的一样,JSONP 也可以自动发现。

Table of Contents

JSONP maven dependencies/changes
REST API code
Model bean changes
Manually adding JsonProcessingFeature

JSONP Maven 依赖项/更改

JSONP 媒体模块是 Jersey2.x 中的模块之一,您无需明确注册其功能,例如JsonProcessingFeature。 一旦 Jersey 检测到添加了它的存在,它就会自动注册。 因此,只需在pom.xml中添加 JSONP 依赖项就可以完成一半的工作。

<dependency>
	<groupId>org.glassfish.jersey.media</groupId>
	<artifactId>jersey-media-json-processing</artifactId>
	<version>2.19</version>
</dependency>

REST API 代码

在编写 API 的服务端,您需要使用@Produces(MediaType.APPLICATION_JSON)注解启用 JSON 媒体类型。

JerseyService.java

@Path("/employees")
public class JerseyService 
{
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public Employees getAllEmployees() 
	{
		Employees list = new Employees();
		list.setEmployeeList(new ArrayList<Employee>());

		list.getEmployeeList().add(new Employee(1, "Lokesh Gupta"));
		list.getEmployeeList().add(new Employee(2, "Alex Kolenchiskey"));
		list.getEmployeeList().add(new Employee(3, "David Kameron"));

		return list;
	}
}	

模型 bean 更改

在模型 bean 方面,您无需放置任何注解或任何配置。 默认情况下它将起作用。 您甚至也不需要添加任何根注解。

Employees.java

public class Employees 
{
	private List<Employee> employeeList;

	public List<Employee> getEmployeeList() {
		return employeeList;
	}

	public void setEmployeeList(List<Employee> employeeList) {
		this.employeeList = employeeList;
	}
}

Employee.java

public class Employee 
{
	private Integer id;
	private String name;

	public Employee() {

	}

	public Employee(Integer id, String name) {
		this.id  = id;
		this.name = name;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + "]";
	}
}

手动添加JsonProcessingFeature

尽管org.glassfish.jersey.jsonp.JsonProcessingFeature是自动注册的,但是如果您希望手动注册,则可以按以下配置添加它。

public class CustomApplication extends ResourceConfig 
{
	public CustomApplication() 
	{
		register(JsonProcessingFeature.class);
		packages("com.howtodoinjava.jersey");
		packages("org.glassfish.jersey.examples.jsonp");
		register(LoggingFilter.class);
		property(JsonGenerator.PRETTY_PRINTING, true);

	}
}

并将此Application类添加到web.xml文件中。

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>

	<display-name>Archetype Created Web Application</display-name>

	<servlet>
		<servlet-name>jersey-serlvet</servlet-name>
		<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
		<init-param>
			<param-name>javax.ws.rs.Application</param-name>
			<param-value>com.howtodoinjava.jersey.CustomApplication</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>jersey-serlvet</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>

</web-app>

在下面将您的问题和评论发送给我。

祝您学习愉快!

参考https://jersey.java.net/documentation/latest/media.html#json.json-p

Jersey + Google Gson 示例

原文: https://howtodoinjava.com/jersey/jax-rs-gson-example/

本教程说明如何将 google Gson 与 Jersey2.x 结合使用。 还有其他一些 Java 库也可以执行此转换,例如 MOXy ,Jackson 或 JSONP ; 但是 Gson 很少需要任何预先注解的 Java 类或 Java 类源代码。

Table of Contents

Maven dependencies/changes
Create Gson Provider
Register Gson Provider
Extending Gson Functionality
Demo

Jersey(Jersey)不提供对 Gson 的任何内置支持,因为它提供了 MOXy 和 JSONP 等。但是,Jersey(Jersey)提供了发现机制,可通过实现一些接口并将实现注册到应用配置中来扫描应用的各种可配置组件。

通过实现接口javax.ws.rs.ext.MessageBodyWriterjavax.ws.rs.ext.MessageBodyReader,可以将 Gson 与 Jersey 一起用作 JSON 序列化器/反序列化器,并可以使用@Provider注解或ResourceConfig.register()方法注册配置。

让我们一步一步地查看所有步骤。

Maven 依赖

首先包括 Gson 依赖项。

<dependency>
	<groupId>com.google.code.gson</groupId>
	<artifactId>gson</artifactId>
	<version>2.2.4</version>
</dependency>

创建 Gson 供应器

现在,创建一个实现接口javax.ws.rs.ext.MessageBodyWriterjavax.ws.rs.ext.MessageBodyReader的类,并覆盖其方法。

package com.howtodoinjava.jersey.provider;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class GsonMessageBodyHandler implements MessageBodyWriter<Object>,
		MessageBodyReader<Object> {
	private static final String UTF_8 = "UTF-8";

	private Gson gson;

	//Customize the gson behavior here
	private Gson getGson() {
		if (gson == null) {
			final GsonBuilder gsonBuilder = new GsonBuilder();
			gson = gsonBuilder.disableHtmlEscaping()
					.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
					.setPrettyPrinting()
					.serializeNulls()
					.create();
		}
		return gson;
	}

	@Override
	public boolean isReadable(Class<?> type, Type genericType,
			java.lang.annotation.Annotation[] annotations, MediaType mediaType) {
		return true;
	}

	@Override
	public Object readFrom(Class<Object> type, Type genericType,
			Annotation[] annotations, MediaType mediaType,
			MultivaluedMap<String, String> httpHeaders, InputStream entityStream) {
		InputStreamReader streamReader = null;
		try {
			streamReader = new InputStreamReader(entityStream, UTF_8);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		try {
			Type jsonType;
			if (type.equals(genericType)) {
				jsonType = type;
			} else {
				jsonType = genericType;
			}
			return getGson().fromJson(streamReader, jsonType);
		} finally {
			try {
				streamReader.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public boolean isWriteable(Class<?> type, Type genericType,
			Annotation[] annotations, MediaType mediaType) {
		return true;
	}

	@Override
	public long getSize(Object object, Class<?> type, Type genericType,
			Annotation[] annotations, MediaType mediaType) {
		return -1;
	}

	@Override
	public void writeTo(Object object, Class<?> type, Type genericType,
			Annotation[] annotations, MediaType mediaType,
			MultivaluedMap<String, Object> httpHeaders,
			OutputStream entityStream) throws IOException,
			WebApplicationException {
		OutputStreamWriter writer = new OutputStreamWriter(entityStream, UTF_8);
		try {
			Type jsonType;
			if (type.equals(genericType)) {
				jsonType = type;
			} else {
				jsonType = genericType;
			}
			getGson().toJson(object, jsonType, writer);
		} finally {
			writer.close();
		}
	}
}

注册 Gson 供应器

现在使用应用资源配置在上述类中注册。

package com.howtodoinjava.jersey;

import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ResourceConfig;
import com.howtodoinjava.jersey.provider.GsonMessageBodyHandler;

public class CustomApplication extends ResourceConfig 
{
	public CustomApplication() 
	{
		packages("com.howtodoinjava.jersey");
		register(LoggingFilter.class);
		register(GsonMessageBodyHandler.class);
	}
}

扩展 Gson 功能

您可以随时在GsonMessageBodyHandler.getGson()方法中扩展/自定义 Gson 行为。

private Gson getGson() {
	if (gson == null) {
		final GsonBuilder gsonBuilder = new GsonBuilder();
		gson = gsonBuilder.disableHtmlEscaping()
				.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
				.setPrettyPrinting()
				.serializeNulls()
				.create();
	}
	return gson;
}

演示

现在,如果您通过任何 REST 客户端访问任何 REST 资源,您将获得所需的 JSON 输出。

点击 URL:http://localhost:8080/JerseyDemos/rest/employees

现在的输出将是:

{
  "EmployeeList": [
    {
      "Id": 1,
      "Name": "Lokesh Gupta"
    },
    {
      "Id": 2,
      "Name": "Alex Kolenchiskey"
    },
    {
      "Id": 3,
      "Name": "David Kameron"
    }
  ]
}

祝您学习愉快!

Jersey REST API 安全示例

原文: https://howtodoinjava.com/jersey/jersey-rest-security/

在此 Jersey rest 安全示例中,我们将学习通过基本认证保护 Jersey REST API 的安全。 这将强制每个用户提供用户名/密码以认证到门户。 另外,用户还必须具有一定级别的角色。 我从为 RESTEasy API 安全性创建的另一个示例扩展了此示例,并使用ContainerRequestFilter实现在用户登陆实际的 REST API 之前验证其访问权限。

Table of Contents

1\. Create request authentication filter
2\. Register AuthenticationFilter with ResourceConfig
3\. Secure REST APIs
4\. Test Jersey AuthenticationFilter

1. 创建请求认证过滤器

我们知道 JAX-RS 2.0 具有用于处理请求前和请求的过滤器,因此我们将使用ContainerRequestFilter接口。 在此过滤器中,我们将获取请求尝试访问的方法的详细信息。

我们将找出该方法上所有与安全性相关的配置,并在此过滤器中验证所有内容,例如,注解,例如@PermitAll@DenyAll@RolesAllowed

根据方法上的注解,我们将决定是通过还是阻止请求。

package com.howtodoinjava.jersey.provider;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;

import org.glassfish.jersey.internal.util.Base64;

/**
 * This filter verify the access permissions for a user
 * based on username and passowrd provided in request
 * */
@Provider
public class AuthenticationFilter implements javax.ws.rs.container.ContainerRequestFilter
{

	@Context
    private ResourceInfo resourceInfo;

    private static final String AUTHORIZATION_PROPERTY = "Authorization";
    private static final String AUTHENTICATION_SCHEME = "Basic";

    @Override
    public void filter(ContainerRequestContext requestContext)
    {
        Method method = resourceInfo.getResourceMethod();
        //Access allowed for all
        if( ! method.isAnnotationPresent(PermitAll.class))
        {
            //Access denied for all
            if(method.isAnnotationPresent(DenyAll.class))
            {
                requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
                         .entity("Access blocked for all users !!").build(););
                return;
            }

            //Get request headers
            final MultivaluedMap<String, String> headers = requestContext.getHeaders();

            //Fetch authorization header
            final List<String> authorization = headers.get(AUTHORIZATION_PROPERTY);

            //If no authorization information present; block access
            if(authorization == null || authorization.isEmpty())
            {
                requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
                    .entity("You cannot access this resource").build());
                return;
            }

            //Get encoded username and password
            final String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", "");

            //Decode username and password
            String usernameAndPassword = new String(Base64.decode(encodedUserPassword.getBytes()));;

            //Split username and password tokens
            final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
            final String username = tokenizer.nextToken();
            final String password = tokenizer.nextToken();

            //Verifying Username and password
            System.out.println(username);
            System.out.println(password);

            //Verify user access
            if(method.isAnnotationPresent(RolesAllowed.class))
            {
                RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
                Set<String> rolesSet = new HashSet<String>(Arrays.asList(rolesAnnotation.value()));

                //Is user valid?
                if( ! isUserAllowed(username, password, rolesSet))
                {
                    requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
                        .entity("You cannot access this resource").build(););
                    return;
                }
            }
        }
    }
    private boolean isUserAllowed(final String username, final String password, final Set<String> rolesSet)
    {
        boolean isAllowed = false;

        //Step 1\. Fetch password from database and match with password in argument
        //If both match then get the defined role for user from database and continue; else return isAllowed [false]
        //Access the database and do this part yourself
        //String userRole = userMgr.getUserRole(username);

        if(username.equals("howtodoinjava") && password.equals("password"))
        {
        	String userRole = "ADMIN";

            //Step 2\. Verify user role
            if(rolesSet.contains(userRole))
            {
                isAllowed = true;
            }
        }
        return isAllowed;
    }
}

2. 向ResourceConfig注册AuthenticationFilter

现在,您需要在ResourceConfig实例上方注册过滤器。 因此,创建一个如下所示的实例:

package com.howtodoinjava.jersey;

import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ResourceConfig;
import com.howtodoinjava.jersey.provider.AuthenticationFilter;
import com.howtodoinjava.jersey.provider.GsonMessageBodyHandler;

public class CustomApplication extends ResourceConfig 
{
	public CustomApplication() 
	{
		packages("com.howtodoinjava.jersey");
		register(LoggingFilter.class);
		register(GsonMessageBodyHandler.class);

		//Register Auth Filter here
		register(AuthenticationFilter.class);
	}
}

并将此资源配置添加到web.xml文件中。

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>

	<display-name>Archetype Created Web Application</display-name>

	<servlet>
		<servlet-name>jersey-serlvet</servlet-name>
		<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
		<init-param>
			<param-name>javax.ws.rs.Application</param-name>
			<param-value>com.howtodoinjava.jersey.CustomApplication</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>jersey-serlvet</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>

</web-app>

3. 安全的 REST API

现在是时候保护 REST API 了。 如下使用标准的 JAX-RS 注解。

@Path("/employees")
public class JerseyService 
{
	@RolesAllowed("ADMIN")
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Consumes(MediaType.APPLICATION_JSON)
	public Employees getAllEmployees() 
	{
		Employees list = new Employees();
		list.setEmployeeList(new ArrayList<Employee>());

		list.getEmployeeList().add(new Employee(1, "Lokesh Gupta"));
		list.getEmployeeList().add(new Employee(2, "Alex Kolenchiskey"));
		list.getEmployeeList().add(new Employee(3, "David Kameron"));

		return list;
	}
}	

4. 测试 Jersey AuthenticationFilter

让我们测试认证设置是否有效。

点击 URL:http://localhost:8080/JerseyDemos/rest/employees

Jersey authentication failure request

Jersey 认证失败的请求

在基本认证参数中传递用户名和密码:howtodoinjava/password

Jersey authenticated success request

Jersey 认证成功的请求

单击下面的链接下载 jersey rest api 认证示例应用的源代码。

JerseyDemos

学习愉快!

M2_REPO – 在 Eclipse 中更改 Maven 仓库的位置

原文: https://howtodoinjava.com/maven/maven-m2-repo-eclipse/

可以从 Eclipse IDE 中读取 Maven 依赖项,该 Maven 通过M2_REPO类路径变量公开。 m2_repo指向工作区中 maven 的本地仓库位置。

在使用 maven 的依赖项之前,必须在构建路径上将m2_repo类变量添加到 eclipse 的类路径变量中。

1. 在 Eclipse 中手动更改 Maven 仓库位置

1.1 导航到“窗口 > 首选项”

eclipse-preferences

1.2 导航到“Java > 构建路径 > Classpath 变量”

classpath_variables_for_projects

1.3 定义新的类路径变量M2_REPO

创建新变量M2_REPO并将其指向 Maven 本地仓库位置。

add_maven_repository

1.4 确认已添加变量

m2_repo_variable_added

2. 从命令行 – eclipse:configure-workspace更新M2_REPO

或者,您可以通过 Maven 命令"eclipse:configure-workspace"定义并添加M2_REPO

例如,这是我系统中命令的输出。

Microsoft Windows [Version 10.0.17134.228]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Users\Lokesh>mvn -Declipse.workspace="E:\devsetup\workspace" eclipse:configure-workspace
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] --- maven-eclipse-plugin:2.10:configure-workspace (default-cli) @ standalone-pom ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.815 s
[INFO] Finished at: 2018-09-09T14:50:20+05:30
[INFO] ------------------------------------------------------------------------

Add m2_repo from command prompt

从命令行添加m2_repo

学习愉快!

Jersey 客户端

Jersey 客户端示例 – Jersey2 客户端 API

原文: https://howtodoinjava.com/jersey/jersey-restful-client-examples/

Jersey 2 客户端 API 从专有的 Jersey 1.x 客户端 API 中得到启发。 在此 Jersey 客户端示例中,我们将学习构建客户端 API 并调用不同的 REST 方法并使用 API​​ 结果。

Table of Contents

1\. Jersey Client Maven
2\. Jersey ClientBuilder
3\. HTTP GET - Collection/List of Entities
4\. HTTP GET - Single Entity
5\. HTTP POST
6\. HTTP PUT
7\. HTTP DELETE
8\. Model classes and Configuration files

1. Jersey 客户端 Maven

pom.xml文件中添加 jersey 客户端 maven 依赖项。

<dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-client</artifactId>
    <version>2.25.1</version>
</dependency>

2. Jersey ClientBuilder

JAX-RS 客户端 API 设计为允许流畅的编程模型。 要创建 Jersey 客户端,请按照以下步骤操作:

  1. 使用ClientBuilder.newClient()静态方法。
  2. 在上面获得的客户端实例上使用client.target()方法。
  3. 在第二步中获得的WebTarget实例上,使用webTarget.request()方法获取Invocation.Builder
  4. 执行invocationBuilder.get()put()post()delete()方法以调用相应的 REST API。
Client client = ClientBuilder.newClient( new ClientConfig().register( LoggingFilter.class ) );

WebTarget webTarget = client.target("http://localhost:8080/JerseyDemos/rest").path("employees");

Invocation.Builder invocationBuilder =  webTarget.request(MediaType.APPLICATION_XML);

Response response = invocationBuilder.post(Entity.entity(emp, MediaType.APPLICATION_XML));

下面给出的示例将有助于我们更好地理解并为开发任务做好准备。 我建议您浏览其他 Jersey 示例,例如文件上传示例文件下载示例

3. HTTP GET – 实体的集合/列表

REST API

这是用于检索系统中所有员工的 API 代码。

@GET
@Path("/employees")
@Produces(MediaType.APPLICATION_XML)
public Employees getAllEmployees() 
{
	Employees list = new Employees();
	list.setEmployeeList(new ArrayList<Employee>());

	list.getEmployeeList().add(new Employee(1, "Lokesh Gupta"));
	list.getEmployeeList().add(new Employee(2, "Alex Kolenchiskey"));
	list.getEmployeeList().add(new Employee(3, "David Kameron"));

	return list;
}

Jersey 客户端代码

此 RESTful 客户端代码将访问上述 API,并在控制台中打印响应。

Client client = ClientBuilder.newClient( new ClientConfig().register( LoggingFilter.class ) );
WebTarget webTarget = client.target("http://localhost:8080/JerseyDemos/rest").path("employees");

Invocation.Builder invocationBuilder =	webTarget.request(MediaType.APPLICATION_XML);
Response response = invocationBuilder.get();

Employees employees = response.readEntity(Employees.class);
List<Employee> listOfEmployees = employees.getEmployeeList();

System.out.println(response.getStatus());
System.out.println(Arrays.toString( listOfEmployees.toArray(new Employee[listOfEmployees.size()]) ));

Output:

200
[Employee [id=1, name=Lokesh Gupta], Employee [id=2, name=Alex Kolenchiskey], Employee [id=3, name=David Kameron]]

4. HTTP GET – 单一实体

这是 API 代码,用于根据其 ID 检索单个员工。

@GET
@Path("/employees/{id}")
@Produces(MediaType.APPLICATION_XML)
public Response updateEmployeeById(@PathParam("id") Integer id) 
{
	if(id  < 0){
		return Response.noContent().build();
	}
	Employee emp = new Employee();

	emp.setId(id);
	emp.setName("Lokesh Gupta");

	GenericEntity<Employee> entity = new GenericEntity<Employee>(emp, Employee.class);
	return Response.ok().entity(entity).build();
}

客户端代码

此 RESTful 客户端代码将访问上述 API,并在控制台中打印响应。

Client client = ClientBuilder.newClient( new ClientConfig().register( LoggingFilter.class ) );
WebTarget webTarget = client.target("http://localhost:8080/JerseyDemos/rest").path("employees").path("1");

Invocation.Builder invocationBuilder =	webTarget.request(MediaType.APPLICATION_XML);
Response response = invocationBuilder.get();

Employee employee = response.readEntity(Employee.class);

System.out.println(response.getStatus());
System.out.println(employee);

Output:

200
Employee [id=1, name=Lokesh Gupta]

5. HTTP POST

这是用于在集合中添加员工的 API 代码。

@POST
@Path("/employees")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public Response addEmployee( Employee e ) throws URISyntaxException 
{
	if(e == null){
		return Response.status(400).entity("Please add employee details !!").build();
	}

	if(e.getName() == null) {
		return Response.status(400).entity("Please provide the employee name !!").build();
	}

	return Response.created(new URI("/rest/employees/"+e.getId())).build();
}

Jersey 客户端代码

此 RESTful 客户端代码将访问上述 API,并在控制台中打印响应。

Client client = ClientBuilder.newClient( new ClientConfig().register( LoggingFilter.class ) );
WebTarget webTarget = client.target("http://localhost:8080/JerseyDemos/rest").path("employees");

Employee emp = new Employee();
emp.setId(1);
emp.setName("David Feezor");

Invocation.Builder invocationBuilder =	webTarget.request(MediaType.APPLICATION_XML);
Response response = invocationBuilder.post(Entity.entity(emp, MediaType.APPLICATION_XML));

System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));

Output:

201

6. HTTP PUT

这是 API 代码,用于通过其 ID 更新员工名称。

@PUT
@Path("/employees/{id}")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public Response updateEmployeeById(@PathParam("id") Integer id, Employee e) 
{
	Employee updatedEmployee = new Employee();

	if(e.getName() == null) {
		return Response.status(400).entity("Please provide the employee name !!").build();
	}

	updatedEmployee.setId(id);
	updatedEmployee.setName(e.getName());

	return Response.ok().entity(updatedEmployee).build();
}

Jersey2 客户端代码

此 RESTful 客户端代码将访问上述 API,并在控制台中打印响应。

Client client = ClientBuilder.newClient( new ClientConfig().register( LoggingFilter.class ) );
WebTarget webTarget = client.target("http://localhost:8080/JerseyDemos/rest").path("employees").path("1");

Employee emp = new Employee();
emp.setId(1);
emp.setName("David Feezor");

Invocation.Builder invocationBuilder =	webTarget.request(MediaType.APPLICATION_XML);
Response response = invocationBuilder.put(Entity.entity(emp, MediaType.APPLICATION_XML));

Employee employee = response.readEntity(Employee.class);

System.out.println(response.getStatus());
System.out.println(employee);

Output:

200
Employee [id=1, name=David Feezor]

7. HTTP DELETE

这是用于通过 ID 从集合中删除员工的 API 代码。

@DELETE
@Path("/employees/{id}")
public Response deleteEmployeeById(@PathParam("id") Integer id) 
{		
	return Response.status(202).entity("Employee deleted successfully !!").build();
}

Jersey 客户端代码

此 RESTful 客户端代码将访问上述 API,并在控制台中打印响应。

Client client = ClientBuilder.newClient( new ClientConfig().register( LoggingFilter.class ) );
WebTarget webTarget = client.target("http://localhost:8080/JerseyDemos/rest").path("employees").path("1");

Invocation.Builder invocationBuilder =	webTarget.request();
Response response = invocationBuilder.delete();

System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));

Output:

202
Employee deleted successfully !!

8. 模型类和配置文件

下面列出了其他用于创建此 Jersey2 客户端示例的文件。

Employees.java

package com.howtodoinjava.jersey;

import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "employeeList")
@XmlAccessorType (XmlAccessType.FIELD)
public class Employees 
{
	@XmlElement(name="employee")
	private List<Employee> employeeList;

	public List<Employee> getEmployeeList() {
		return employeeList;
	}

	public void setEmployeeList(List<Employee> employeeList) {
		this.employeeList = employeeList;
	}
}

Employee.java

package com.howtodoinjava.jersey;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "employee")
@XmlAccessorType (XmlAccessType.FIELD)
public class Employee 
{
	private Integer id;
	private String name;

	public Employee() {

	}

	public Employee(Integer id, String name) {
		this.id  = id;
		this.name = name;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + "]";
	}
}

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd;
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.howtodoinjava.jersey</groupId>
	<artifactId>JerseyDemos</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<repositories>
		<repository>
			<id>maven2-repository.java.net</id>
			<name>Java.net Repository for Maven</name>
			<url>http://download.java.net/maven/2/</url>
			<layout>default</layout>
		</repository>
	</repositories>
	<properties>
		<jersey2.version>2.19</jersey2.version>
		<jaxrs.version>2.0.1</jaxrs.version>
	</properties>
	<dependencies>
		<!-- JAX-RS -->
		<dependency>
			<groupId>javax.ws.rs</groupId>
			<artifactId>javax.ws.rs-api</artifactId>
			<version>${jaxrs.version}</version>
		</dependency>
		<!-- Jersey2.19 -->
		<dependency>
			<groupId>org.glassfish.jersey.containers</groupId>
			<artifactId>jersey-container-servlet</artifactId>
			<version>${jersey2.version}</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.jersey.core</groupId>
			<artifactId>jersey-server</artifactId>
			<version>${jersey2.version}</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.jersey.core</groupId>
			<artifactId>jersey-client</artifactId>
			<version>${jersey2.version}</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.jersey.media</groupId>
			<artifactId>jersey-media-multipart</artifactId>
			<version>${jersey2.version}</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.jaxrs</groupId>
			<artifactId>jackson-jaxrs-json-provider</artifactId>
			<version>2.4.1</version>
		</dependency>
	</dependencies>
	<build>
		<finalName>JerseyDemos</finalName>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>

	<display-name>Archetype Created Web Application</display-name>

	<servlet>
		<servlet-name>jersey-serlvet</servlet-name>
		<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
		<init-param>
			<param-name>jersey.config.server.provider.packages</param-name>
			<param-value>com.howtodoinjava.jersey</param-value>
		</init-param>
		<init-param>
			<param-name>jersey.config.server.provider.classnames</param-name>
			<param-value>org.glassfish.jersey.filter.LoggingFilter</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>jersey-serlvet</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>

</web-app>

在下面将您的问题和评论发送给我。

学习愉快!

参考:

Jersey 客户端 Java 文档

Jersey REST 客户端认证示例

原文: https://howtodoinjava.com/jersey/jersey-rest-client-authentication/

了解如何使用HttpAuthenticationFeature构建Jersey REST 客户端,该客户端可用于访问认证/授权安全性后面的 REST API。 例如,我们将为在 Jersey Secured REST API 教程中保护的服务创建 jersey 客户端; 并且我将扩展为 Jersey RESTful 客户端示例创建的源代码。

Table of Contents

1\. HttpAuthenticationFeature
2\. How to secure REST APIs
3\. Jersey REST Client Code

1. Jersey 客户端 – HttpAuthenticationFeature

HttpAuthenticationFeature类提供 HttpBasic 和 Digest 客户端认证功能。 该功能以 4 种模式之一工作,即BASICBASIC NON-PREEMPTIVEDIGESTUNIVERSAL。 让我们快速了解它们。

  1. BASIC – 一种抢占式认证方式,即信息始终与每个 HTTP 请求一起发送。 此模式必须与 SSL/TLS 结合使用,因为密码仅以 BASE64 编码发送。
  2. BASIC NON-PREEMPTIVE – 一种非优先的认证方式,即仅当服务器拒绝带有 401 状态码的请求后再添加认证信息,才添加认证信息。
  3. DIGEST – HTTP 摘要认证。 不需要使用 SSL/TLS。
  4. UNIVERSAL – 非抢占模式下基本认证和摘要认证的组合,即在 401 响应的情况下,将根据WWW-Authenticate HTTP 标头中定义的请求认证使用适当的认证。

要使用HttpAuthenticationFeature,请构建一个实例并向客户端注册。

1.1 基本认证方式

HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("username", "password");

final Client client = ClientBuilder.newClient();
client.register(feature);

1.2 基本认证 - 非强制模式

 HttpAuthenticationFeature feature = HttpAuthenticationFeature.basicBuilder()
     								.nonPreemptive()
     								.credentials("username", "password")
     								.build();

final Client client = ClientBuilder.newClient();
client.register(feature);

1.3 通用模式

//HttpAuthenticationFeature feature = HttpAuthenticationFeature.universal("username", "password");

//Universal builder having different credentials for different schemes
HttpAuthenticationFeature feature = HttpAuthenticationFeature.universalBuilder()
				.credentialsForBasic("username1", "password1")
				.credentials("username2", "password2").build();

final Client client = ClientBuilder.newClient();
client.register(feature);

2. 如何保护 REST API

对于启用认证的 REST api,请使用与角色相关的注解,例如@RolesAllowed。 例如,这是安全的 REST API 的代码。

@Path("/employees")
public class JerseyService 
{
	@RolesAllowed("ADMIN")
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Consumes(MediaType.APPLICATION_JSON)
	public Employees getAllEmployees() 
	{
		Employees list = new Employees();
		list.setEmployeeList(new ArrayList<Employee>());

		list.getEmployeeList().add(new Employee(1, "Lokesh Gupta"));
		list.getEmployeeList().add(new Employee(2, "Alex Kolenchiskey"));
		list.getEmployeeList().add(new Employee(3, "David Kameron"));

		return list;
	}
}

阅读更多:Jersey 安全 REST API 教程

3. Jersey REST 客户端代码

以下是 Jersey REST 客户端基本认证示例,该示例接受用于认证的用户名和密码详细信息。

public static void main(String[] args) throws IOException 
{
	httpGETCollectionExample();
}

private static void httpGETCollectionExample() 
{
	ClientConfig clientConfig = new ClientConfig();

	HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("howtodoinjava", "password");
	clientConfig.register( feature) ;

	clientConfig.register(JacksonFeature.class);

	Client client = ClientBuilder.newClient( clientConfig );
	WebTarget webTarget = client.target("http://localhost:8080/JerseyDemos/rest").path("employees");

	Invocation.Builder invocationBuilder =	webTarget.request(MediaType.APPLICATION_JSON);
	Response response = invocationBuilder.get();

	System.out.println(response.getStatus());
	System.out.println(response.getStatusInfo());

	if(response.getStatus() == 200)
	{
		Employees employees = response.readEntity(Employees.class);
		List<Employee> listOfEmployees = employees.getEmployeeList();
		System.out.println(Arrays.toString( listOfEmployees.toArray(new Employee[listOfEmployees.size()]) ));
	}
}

3.1 正确的用户名/密码的输出

200
OK
[Employee [id=1, name=Lokesh Gupta], Employee [id=2, name=Alex Kolenchiskey], Employee [id=3, name=David Kameron]]

3.2 不正确的用户名/密码的输出

401
Unauthorized

将您的问题放在评论部分。

学习愉快!

Jersey 客户端 - 设置 Cookie 示例

原文: https://howtodoinjava.com/jersey/jersey-client-cookie-example/

在本示例中,我们将学习在 Jersey 客户端发起的 HTTP 请求中设置 cookie。 本示例利用Invocation.Builder将 Cookie 设置为外发 REST 调用。

要在 REST API 请求中设置 Cookie,请先从webTarget.request()方法中获取Invocation.Builder的引用,然后再使用它的方法。

Client client = ClientBuilder.newClient( new ClientConfig().register( LoggingFilter.class ) );
WebTarget webTarget = client.target("http://localhost:8080/JerseyDemos/rest").path("employees");

Invocation.Builder invocationBuilder =  webTarget.request(MediaType.APPLICATION_JSON);
Response response = invocationBuilder
							.cookie("cookieParam1","cookieValue1")
							.cookie(new Cookie("cookieParam2", "cookieValue2"))
							.get();

Employees employees = response.readEntity(Employees.class);
//More code

REST API 代码

我在下面编写了 REST API 进行测试。

@GET
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Employees getAllEployees(@CookieParam(value="cookieParam1") String cookieParam1,
								@CookieParam(value="cookieParam2") String cookieParam2) 
{
	System.out.println("cookieParam1 is :: "+ cookieParam1);
	System.out.println("cookieParam2 is :: "+ cookieParam2);

	Employees list = new Employees();
	list.setEmployeeList(new ArrayList<Employee>());

	list.getEmployeeList().add(new Employee(1, "Lokesh Gupta"));
	list.getEmployeeList().add(new Employee(2, "Alex Kolenchiskey"));
	list.getEmployeeList().add(new Employee(3, "David Kameron"));

	return list;
}

演示

现在,按照第一个标题中的建议,使用 Jersey 客户端代码在 REST API 之上进行调用。

public static void main(String[] args) 
{
	Client client = ClientBuilder.newClient( new ClientConfig().register( LoggingFilter.class ) );
	WebTarget webTarget = client.target("http://localhost:8080/JerseyDemos/rest").path("employees");

	Invocation.Builder invocationBuilder =  webTarget.request(MediaType.APPLICATION_JSON);
	Response response = invocationBuilder
								.cookie("cookieParam1","cookieValue1")
								.cookie(new Cookie("cookieParam2", "cookieValue2"))
								.get();

	Employees employees = response.readEntity(Employees.class);
	List<Employee> listOfEmployees = employees.getEmployeeList();

	System.out.println(response.getStatus());
	System.out.println(Arrays.toString( listOfEmployees.toArray(new Employee[listOfEmployees.size()]) ));
}

Output: 

On client side:

Oct 01, 2015 4:53:59 PM org.glassfish.jersey.filter.LoggingFilter log
INFO: 1 * Sending client request on thread main
1 > GET http://localhost:8080/JerseyDemos/rest/employees
1 > Accept: application/json
1 > Cookie: $Version=1;cookieParam1=cookieValue1,$Version=1;cookieParam2=cookieValue2

On server side:

cookieParam1 is :: cookieValue1
cookieParam2 is :: cookieValue2

祝您学习愉快!

JDBC 教程

Java JDBC 教程

原文: https://howtodoinjava.com/jdbc-tutorials/

Java 数据库连接(JDBC)API 提供了来自 Java 的通用数据访问。 使用 JDBC API,您几乎可以访问任何数据源,从关系数据库到电子表格和平面文件。

JDBC API 由两个包组成:

这两个包都是通过 Java 平台标准版(Java SE)下载的。

JDBC 基础

JDBC CRUD 示例

JDBC PreparedStatement

JDBC 最佳实践

相关的 SQL 教程

JDBC 资源

Java – JDBC 连接示例(MySQL)

原文: https://howtodoinjava.com/java/jdbc/jdbc-mysql-database-connection-example/

如果您仍然在项目中使用 JDBC 进行数据库访问,这很奇怪,因为有很多功能强大的替代方案,例如 HiberateiBatis。 但是学习基础知识很重要,并且需要先学习 JDBC。

JDBC-Icon

在本文中,我将提供一个示例,该示例使用 MySQL 驱动与数据库建立连接。 阅读有关 JDBC 驱动的类型的更多信息。

处理连接需要执行以下步骤:

1)加载驱动

2)打开数据库连接

3)关闭数据库连接

让我们按照上面的代码步骤进行操作:

1)加载 JDBC 驱动

最简单的方法是在实现java.sql.Driver接口的类上使用Class.forName()。 对于 MySQL Connector/J,此类的名称为com.mysql.jdbc.Driver。 使用此方法,您可以使用外部配置文件来提供连接到数据库时要使用的驱动类名称和驱动参数。

	Class.forName("com.mysql.jdbc.Driver");

作为初始化的一部分,DriverManager类将尝试加载在“jdbc.drivers”系统属性中引用的驱动类。 这允许用户自定义其应用使用的 JDBC 驱动程序。

2)打开数据库连接

DriverManager中注册了驱动之后,可以通过调用DriverManager.getConnection()获得连接到特定数据库的Connection实例:

	Connection connection = DriverManager
		.getConnection("jdbc:mysql://localhost:3306/JDBCDemo", "root", "password");

建立连接后,就可以使用它来创建StatementPreparedStatement对象,以及检索有关数据库的元数据。

3)关闭数据库连接

此步骤与打开连接一样重要。 任何打开的连接都会浪费资源,并导致各种异常。

try 
{
	if(connection != null)
		connection.close();
	System.out.println("Connection closed !!");
} catch (SQLException e) {
	e.printStackTrace();
}

完整的 JDBC 连接示例

让我们在下面的示例中查看整个过程:

package com.howtodoinjava.jdbc.demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionDemo {
	public static void main(String[] argv) {

		System.out.println("-------- MySQL JDBC Connection Demo ------------");
		try 
		{
			Class.forName("com.mysql.jdbc.Driver");
		} 
		catch (ClassNotFoundException e) {
			System.out.println("MySQL JDBC Driver not found !!");
			return;
		}
		System.out.println("MySQL JDBC Driver Registered!");
		Connection connection = null;
		try {
			connection = DriverManager
				.getConnection("jdbc:mysql://localhost:3306/JDBCDemo", "root", "password");
			System.out.println("SQL Connection to database established!");

		} catch (SQLException e) {
			System.out.println("Connection Failed! Check output console");
			return;
		} finally {
			try 
			{
				if(connection != null)
					connection.close();
				System.out.println("Connection closed !!");
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

Output:

-------- MySQL JDBC Connection Demo ------------
MySQL JDBC Driver Registered!
SQL Connection to database established!
Connection closed !!

仅此而已。 如果需要更多说明,请发表评论。

祝您学习愉快!

Java – JDBC 驱动类型

原文: https://howtodoinjava.com/java/jdbc/jdbc-basics-types-of-jdbc-drivers/

Java 数据库连接(JDBC)是标准应用编程接口(API)的 JavaSoft 规范,允许 Java 程序访问数据库管理系统。 JDBC API 由一组用 Java 编程语言编写的接口和类组成。 使用这些标准接口和类,程序员可以编写连接到数据库的应用,发送以结构化查询语言(SQL)编写的查询,并处理结果。 JDBC 面向关系数据库。

因为 JDBC 是标准规范,所以使用 JDBC API 的 Java 程序可以连接到任何具有 JDBC 驱动的数据库管理系统(DBMS)。

JDBC 驱动

JDBC API 定义了程序员用来连接数据库和发送查询的 Java 接口和类。

JDBC 驱动程序为特定的 DBMS 供应商实现这些接口和类。

Java 程序(使用 JDBC API)在实际连接到数据库之前为特定的 DBMS 加载指定的驱动。 然后,JDBC 的DriverManager类将所有 JDBC API 调用发送到已加载的驱动。

JDBC 驱动的类型

4 种不同类型的 JDBC 驱动

  1. 类型 1:JDBC-ODBC 桥驱动
  2. 类型 2:本机 API 驱动
  3. 类型 3:所有 Java + 中间件翻译驱动
  4. 类型 4:纯 Java 驱动

让我们一一看一下。

类型 1:JDBC-ODBC 桥驱动

JDBC-driver-type-1

1 类 JDBC 驱动由 Java 部分组成,该 Java 部分将 JDBC 接口转换为 ODBC 调用。 ODBC 桥然后调用给定数据库的 ODBC 驱动,即该驱动将 JDBC 方法调用转换为 ODBC 函数调用。 该驱动依赖于平台,因为它使用 ODBC,而 ODBC 又取决于运行 JVM 的基础操作系统的本机库。 另外,使用此驱动还会导致其他安装依赖项。 例如,必须在具有驱动的计算机上安装 ODBC,并且数据库必须支持 ODBC 驱动。 如果可以使用纯 Java 驱动的替代方案,则不建议使用此驱动。

Sun 提供了 JDBC-ODBC 桥驱动:sun.jdbc.odbc.JdbcOdbcDriver。 该驱动是本机代码,而不是 Java,并且是封闭源代码。

类型 2:本机 API 驱动

JDBC-driver-type-2

2 类 JDBC 驱动类似于 1 类 驱动,不同之处在于 ODBC 部分被替换为本地代码部分。 本机代码部分针对特定的数据库产品,即使用数据库产品的客户端库。 驱动将 JDBC 方法调用转换为数据库本机 API 的本机调用。

这种架构消除了对 ODBC 驱动的需要,而直接称为数据库供应商提供的本机客户端库。 数据库供应商很快就采用了这种方法,因为它可以快速,廉价地实现,因为他们可以重用现有的基于 C/C++ 的本机库。

类型 3:全 Java + 中间件翻译驱动

JDBC-driver-type-3

3 类 JDBC 驱动是一种全 Java 驱动,将 JDBC 接口调用发送到中间服务器。 然后,中间服务器代表 JDBC 驱动连接到数据库。 中间层(应用服务器)将 JDBC 调用直接或间接转换为供应商特定的数据库协议。

3 类驱动试图成为 100% Java 解决方案,但从未真正吸引过很多人。 3 类驱动具有 Java 客户端组件和 Java 服务器组件,后者实际上是在与数据库对话。 尽管从技术上讲这是一个完整的 Java 解决方案,但是数据库供应商不喜欢这种方法,因为这种方法成本高昂 – 他们将不得不重写全部为 C/C++ 的本机客户端库。 此外,这并没有提高架构效率,因为我们实际上仍然是 3 层架构,因此很容易看出为什么它从来都不是流行的选择。

类型 4:纯 Java 驱动

JDBC-driver-type-4

JDBC 4 类驱动,也称为直接数据库纯 Java 驱动,是一种数据库驱动实现,将 JDBC 调用直接转换为供应商特定的数据库协议。 它是针对特定数据库产品实现的。 今天,大多数 JDBC 驱动都是 4 类驱动。

4 类驱动完全用 Java 编写,因此与平台无关。 它们安装在客户端的 Java 虚拟机中。 它提供了比 1 类和 2 类驱动更好的性能,因为它没有将调用转换为 ODBC 或数据库 API 调用的开销。 与 3 类驱动不同,它不需要关联的软件即可工作。

该架构将整个 JDBC API 实现以及用于直接与数据库通信的所有逻辑封装在单个驱动中。 通过在 100% java 程序包中都包含一个单独的层和一个小的驱动,可以轻松进行部署并简化开发过程。

例如,这种类型包括广泛使用的 Oracle 瘦驱动。

“虽然尚未得到 JDBC 专家组的正式批准,但有关 JDBC 社区中新的 5 类驱动提案的讨论很多。”

学习愉快!

JDBC SELECT查询示例

原文: https://howtodoinjava.com/java/jdbc/jdbc-select-query-example/

在以前的文章中,我们了解了 JDBC 驱动的类型如何使用 JDBC 建立数据库连接。 让我们继续前进,并开始与数据库进行交互。 我要举的第一个示例是 SQL SELECT查询

JDBC-Icon

执行 SQL SELECT查询以获取存储在关系数据库中的数据。 它需要执行以下步骤:

1)建立数据库连接
2)执行 SQL 查询
3)从结果集中获取数据

前提条件至少包括设置数据库架构和创建表。

CREATE SCHEMA 'JDBCDemo' ;

CREATE  TABLE 'JDBCDemo'.'EMPLOYEE' 
(
  'ID' INT NOT NULL DEFAULT 0 ,
  'FIRST_NAME' VARCHAR(100) NOT NULL ,
  'LAST_NAME' VARCHAR(100) NULL ,
  'STAT_CD' TINYINT NOT NULL DEFAULT 0 
);

让我们在代码中编写以上步骤:

1)建立数据库连接

尽管我们已经在建立 JDBC 连接中了解了它,但是让我们用这个简单的代码片段来回顾一下。

	Class.forName("com.mysql.jdbc.Driver");
	connection = DriverManager
		.getConnection("jdbc:mysql://localhost:3306/JDBCDemo", "root", "password");

2)执行 SQL 查询

这是帖子中的主要步骤和核心部分。 它需要创建Statement对象,然后使用其executeQuery()方法。

Statement selectStmt = connection.createStatement();
ResultSet rs = selectStmt
	.executeQuery("SELECT ID,FIRST_NAME,LAST_NAME,STAT_CD FROM EMPLOYEE WHERE ID <= 10");

3)从结果集中获取数据

您可以使用ResultSet中提供的各种getXXX()方法。 但是,如果要使其通用,请使用getString()方法并在需要时解析数据。

ResultSet rs = selectStmt
	.executeQuery("SELECT ID,FIRST_NAME,LAST_NAME,STAT_CD FROM EMPLOYEE WHERE ID <= 10");
while(rs.next())
{
	System.out.println(rs.getString(1));	//First Column
	System.out.println(rs.getString(2));	//Second Column
	System.out.println(rs.getString(3));	//Third Column
	System.out.println(rs.getString(4));	//Fourth Column
}

让我们看看整个代码在工作。

package com.howtodoinjava.jdbc.demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class SelectDataDemo {
	public static void main(String[] args) {
		Connection connection = null;
		Statement insertStmt = null;
		Statement selectStmt = null;
		try 
		{
			Class.forName("com.mysql.jdbc.Driver");
			connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/JDBCDemo", "root", "password");

			/*insertStmt = connection.createStatement();
			insertStmt.execute("INSERT INTO EMPLOYEE (ID,FIRST_NAME,LAST_NAME,STAT_CD) VALUES (1,'Lokesh','Gupta',5)");
			insertStmt.execute("INSERT INTO EMPLOYEE (ID,FIRST_NAME,LAST_NAME,STAT_CD) VALUES (2,'howtodoinjava','com',5)");*/

			selectStmt = connection.createStatement();
			ResultSet rs = selectStmt.executeQuery("SELECT ID,FIRST_NAME,LAST_NAME,STAT_CD FROM EMPLOYEE WHERE ID <= 10");
			while(rs.next())
			{
				System.out.println(rs.getString(1));	//First Column
				System.out.println(rs.getString(2));	//Second Column
				System.out.println(rs.getString(3));	//Third Column
				System.out.println(rs.getString(4));	//Fourth Column
			}
		} 
		catch (Exception e) {
			e.printStackTrace();
		}finally {
			try {
				selectStmt.close();
				insertStmt.close();
				connection.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

Output:

1
Lokesh
Gupta
5
2
howtodoinjava
com
5

以上就是这篇文章。 如果需要解释,请给我评论。

快乐学习!

JDBC SQL INSERT查询示例

原文: https://howtodoinjava.com/java/jdbc/jdbc-sql-insert-query-example/

在以前的文章中,我们了解了 ** JDBC 驱动的类型**如何使用 JDBC,然后是建立数据库连接后如何执行SELECT查询。 让我们前进。 在此示例中,我将使用 JDBC 来执行 SQL INSERT查询。

JDBC-Icon

执行 SQL INSERT查询以推送/存储关系数据库中存储的数据。 它需要执行以下步骤:

1)建立数据库连接

2)执行 SQL INSERT查询

先决条件包括至少建立数据库架构和创建表。

CREATE SCHEMA 'JDBCDemo' ;

CREATE  TABLE 'JDBCDemo'.'EMPLOYEE' 
(
  'ID' INT NOT NULL DEFAULT 0 ,
  'FIRST_NAME' VARCHAR(100) NOT NULL ,
  'LAST_NAME' VARCHAR(100) NULL ,
  'STAT_CD' TINYINT NOT NULL DEFAULT 0 
);

让我们在代码中编写以上步骤:

1)建立数据库连接

尽管我们已经在建立 JDBC 连接中学习了它,但是让我们用这个简单的代码片段来回顾一下。

Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager
	.getConnection("jdbc:mysql://localhost:3306/JDBCDemo", "root", "password");

2)执行 SQL INSERT查询

这是此帖子的主要步骤和核心部分。 它需要先创建一个Statement对象,然后使用它的execute()方法。

Statement stmt = connection.createStatement();
stmt.execute("INSERT INTO EMPLOYEE (ID,FIRST_NAME,LAST_NAME,STAT_CD) VALUES (1,'Lokesh','Gupta',5)");

上面的语句将在我们连接的数据库中执行一个插入语句。

让我们看看整个代码在工作。

package com.howtodoinjava.jdbc.demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

public class InsertDataDemo {
	public static void main(String[] args) {
		Connection connection = null;
		Statement stmt = null;
		try 
		{
			Class.forName("com.mysql.jdbc.Driver");
			connection = DriverManager
				.getConnection("jdbc:mysql://localhost:3306/JDBCDemo", "root", "password");

			stmt = connection.createStatement();
			stmt.execute("INSERT INTO EMPLOYEE (ID,FIRST_NAME,LAST_NAME,STAT_CD) "
								+ "VALUES (1,'Lokesh','Gupta',5)");
		} 
		catch (Exception e) {
			e.printStackTrace();
		}finally {
			try {
				stmt.close();
				connection.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

以上就是这篇文章。 如果需要解释,请给我评论。

快乐学习!

Maven 代理设置 – Eclipse,命令行和全局设置

原文: https://howtodoinjava.com/maven/maven-proxy-settings/

学习配置 maven https 代理设置。 默认情况下,maven 在本地系统上运行时使用默认的网络连接。 但是有时我们在工作场所或公司中运行应用。 这些网络通过代理服务器或防火墙来屏蔽互联网使用,因此从我们的系统进行的所有互联网查询都通过此代理服务器进行。

Maven 默认情况下不会检测网络代理配置,并且要在这些受限区域中使用 maven,我们必须为 maven 配置网络代理设置。

阅读更多: Maven 安装

1. 如何配置 Maven 代理设置

要设置 maven 代理设置,请执行以下步骤:

  • 导航到路径 – {M2_HOME} conf/settings.xml
  • 在任何文本编辑器中以编辑模式打开文件settings.xml
  • 打开并更新<proxy>标签。

该标签如下所示:

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

	<!-- proxies
	| This is a list of proxies which can be used on this machine to connect to the network.
	| Unless otherwise specified (by system property or command-line switch), the first proxy
	| specification in this list marked as active will be used.
	|-->
	<proxies>
		<!-- proxy
		 | Specification for one proxy, to be used in connecting to the network.
		 |
		<proxy>
			<id>optional</id>
			<active>true</active>
			<protocol>http</protocol>
			<username>proxyuser</</username>
			<password>proxypass</password>
			<host>proxy.host.net</host>
			<port>80</port>
			<nonProxyHosts>local.net|some.host.com</nonProxyHosts>
		</proxy>
		-->
	</proxies>

</settings>

使用您的网络特定凭据更新上述代理服务器字段。 您可以在浏览器的连接设置中找到网络代理的详细信息。 例如,可以在以下位置找到代理设置:

Internet Explorer >> tools >> internet options >> Connections >> LAN Settings

Firefox >> tools >> options >> ADVANCED TAB >> settings

2. Maven 代理设置示例

给出了示例网络代理设置条目。

<!-- proxies
| This is a list of proxies which can be used on this machine to connect to the network.
| Unless otherwise specified (by system property or command-line switch), the first proxy
| specification in this list marked as active will be used.
|-->
<proxies>
	<!-- proxy
	 | Specification for one proxy, to be used in connecting to the network.
	 |
	<proxy>
		<id>optional</id>
		<active>true</active>
		<protocol>https</protocol>
		<username>lokesh</</username>
		<password>abc123</password>
		<host>webproxy.company.com</host>
		<port>8081</port>
		<!-- <nonProxyHosts>local.net|some.host.com</nonProxyHosts> -->
	</proxy>
	-->
</proxies>

完成上述步骤后,您还应该可以在代理服务器后面使用 maven。

3. Eclipse 中的 Maven 代理设置

要在使用 Maven 时在 eclipse 中使用代理设置,请按照下列步骤操作:

  • 打开 Eclipse,然后转到窗口 -> 首选项。

  • 点击User SettingsBrowse按钮,并选择settings.xml

    Maven proxy in eclipse

    Eclipse 中的 Maven 代理

  • 单击"Update Settings"按钮更新设置。 如果出现任何确认对话框,只需单击“是”。

4. 通过命令行进行 Maven 代理设置

为了节省时间,我们可以在 maven 命令的末尾附加代理详细信息。

$ mvn clean install -DproxySet=true -DproxyHost=ur.proxy.server -DproxyPort=port

尽管可以从命令行设置 Maven 代理,但仍建议在settings.xml文件中使用全局代理设置

学习愉快!

参考:

配置代理

JDBC SQL DELETE查询示例

原文: https://howtodoinjava.com/java/jdbc/jdbc-sql-delete-query-example/

在以前的文章中,我们了解了 JDBC 驱动的类型如何使用 JDBC,然后是建立数据库连接后如何执行SELECT查询,然后是INSET查询示例 。 让我们前进。 在此示例中,我将使用 JDBC 来执行 SQL DELETE查询。

JDBC-Icon

执行 SQL DELETE查询以删除/删除关系数据库中存储的数据。 它需要执行以下步骤:

1)建立数据库连接
2)执行 SQL DELETE查询

先决条件包括设置数据库架构和首先创建表。

CREATE SCHEMA 'JDBCDemo' ;

CREATE TABLE 'JDBCDemo'.'EMPLOYEE'
(
	'ID' INT NOT NULL DEFAULT 0 ,
	'FIRST_NAME' VARCHAR(100) NOT NULL ,
	'LAST_NAME' VARCHAR(100) NULL ,
	'STAT_CD' TINYINT NOT NULL DEFAULT 0
);

让我们在代码中编写以上步骤:

1)建立数据库连接

尽管我们已经在建立 JDBC 连接中学习了它,但是让我们用这个简单的代码片段来回顾一下。

Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager
	.getConnection("jdbc:mysql://localhost:3306/JDBCDemo", "root", "password");

2)执行 SQL DELETE查询

这是帖子中的主要步骤和核心部分。 它需要先创建一个Statement对象,然后使用它的execute()方法。

Statement stmt = connection.createStatement();
stmt.execute("DELETE FROM EMPLOYEE WHERE ID >= 1");

上面的语句将在我们连接的数据库中执行delete语句。 这将删除所有与where子句匹配的记录。

让我们看看整个代码在工作。

package com.howtodoinjava.jdbc.demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

public class DeleteDataDemo {
	public static void main(String[] args) {
		Connection connection = null;
		Statement stmt = null;
		try 
		{
			Class.forName("com.mysql.jdbc.Driver");
			connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/JDBCDemo", "root", "password");

			stmt = connection.createStatement();
			stmt.execute("DELETE FROM EMPLOYEE WHERE ID >= 1");
		} 
		catch (Exception e) {
			e.printStackTrace();
		}finally {
			try {	
				stmt.close();
				connection.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}	
}

以上就是这篇文章。 如果需要解释,请给我评论。

快乐学习!

Java JDBC PreparedStatement示例

原文: https://howtodoinjava.com/java/jdbc/how-to-execute-preparedstatement-using-jdbc/

在数据库管理系统中, 预备语句参数化语句是用于高效重复执行相同或相似数据库语句的功能。 预备语句通常与 SQL 语句(例如查询或更新)一起使用,采用模板的形式,在每次执行期间将某些常量值替换为模板。

一个典型的模板如下所示:“NSERT INTO EMPLOYEE (ID, NAME) VALUES (?, ?);
此处,值在运行时在以“?”表示的占位符处设置。

预备语句如何工作?

大多数关系数据库通过四个步骤来处理 JDBC / SQL 查询:

  1. 解析传入的 SQL 查询
  2. 编译 SQL 查询
  3. 规划/优化数据采集路径
  4. 执行优化的查询/获取并返回数据

对于发送到数据库的每个 SQL 查询,一个Statement将始终执行上述四个步骤。 上面的执行过程中,PreparedStatement预先执行步骤(1)–(3)。 因此,在创建PreparedStatement时,会立即执行一些预优化。 这样做的目的是减轻执行时数据库引擎的负担。

使用预备语句与简单的 JDBC 语句相比的优势

  • SQL 语句的预编译和数据库侧缓存可提高整体执行速度,并能够批量重用同一 SQL 语句。
  • 通过内置对引号和其他特殊字符的转义,自动防止 SQL 注入攻击。 请注意,这要求您使用任何PreparedStatement.setXxx()方法来设置值,并且不能通过字符串连接使用 SQL 字符串中的值。
  • 除了以上两种主要用法外,预备语句还使处理诸如 BLOB 和 CLOB 之类的复杂对象变得容易。

如果您错过了,在以前的文章中,我们了解了 JDBC 驱动的类型和一些基本操作,例如使用 JDBC 建立数据库连接,然后是如何执行SELECT查询,然后单击INSERT查询示例

JDBC-Icon

执行预备语句需要执行以下步骤:

1)建立数据库连接

2)设置值并执行预备语句

前提条件包括至少设置数据库架构和创建表。

CREATE SCHEMA 'JDBCDemo' ;

CREATE TABLE 'JDBCDemo'.'EMPLOYEE'
(
	'ID' INT NOT NULL DEFAULT 0 ,
	'FIRST_NAME' VARCHAR(100) NOT NULL ,
	'LAST_NAME' VARCHAR(100) NULL ,
	'STAT_CD' TINYINT NOT NULL DEFAULT 0
);

让我们在代码中编写以上步骤:

1)建立 JDBC 数据库连接

尽管我们在建立 JDBC 连接时已经了解了它,但是让我们用这个简单的代码片段来回顾一下。

Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager
	.getConnection("jdbc:mysql://localhost:3306/JDBCDemo", "root", "password");

2)设置值并执行PreparedStatement

这是帖子中的主要步骤和核心部分。 它需要创建一个Statement对象,然后使用其executeQuery()方法。

PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, 87);
pstmt.setString(2, "Lokesh");
pstmt.setString(3, "Gupta");
pstmt.setInt(4, 5);

int affectedRows = pstmt.executeUpdate();

让我们看看整个代码在工作。

完整的 JDBC PreparedStatement示例

package com.howtodoinjava.jdbc.demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

public class PreparedStatementDemo 
{
	public static void main(String[] args) 
	{
		Connection connection = null;
		PreparedStatement pstmt = null;
		String sql = "INSERT INTO EMPLOYEE (ID,FIRST_NAME,LAST_NAME,STAT_CD) VALUES (?,?,?,?)";
		try 
		{
			Class.forName("com.mysql.jdbc.Driver");
			connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/JDBCDemo", "root", "password");

			pstmt = connection.prepareStatement(sql);
			pstmt.setInt(1, 87);
			pstmt.setString(2, "Lokesh");
			pstmt.setString(3, "Gupta");
			pstmt.setInt(4, 5);
			int affectedRows = pstmt.executeUpdate();
			System.out.println(affectedRows + " row(s) affected !!");
		} 
		catch (Exception e) {
			e.printStackTrace();
		}finally {
			try {
				pstmt.close();
				connection.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

Output:

1 row(s) affected !!

以上就是这篇文章。 如果需要解释,请给我评论。

快乐学习!

JDBC 性能优化技巧

原文: https://howtodoinjava.com/java/jdbc/best-practices-to-improve-jdbc-performance/

Java 数据库连接(JDBC)是标准应用编程接口(API)的 JavaSoft 规范,允许 Java 程序访问数据库管理系统。 JDBC API 由一组用 Java 编程语言编写的接口和类组成。 使用这些标准接口和类,程序员可以编写连接数据库的应用,发送以结构化查询语言(SQL)编写的查询,并处理结果。 JDBC 面向关系数据库。

虽然不再标准直接在您的应用中使用 JDBC,因为我们有许多更健壮的 API 可以为我们完成这项工作,例如 HiberateiBatis 。 但是,如果由于特定的要求而仍然被您吸引,或者只是在学习它,那么下面的建议将帮助您编写更快速和有效的代码。

JDBC-Icon

Sections in this post:
Use Object Pooling
Consider MetaData Performance
Choose Commit Mode carefully
Save Some Bytes On Network Traffic

让我们直接进入讨论。

几乎始终使用对象池

对象池可以发生在两个方面:

1)连接池:由于在后端数据库中建立网络连接和初始化数据库连接会话会产生开销,因此创建数据库连接通常很昂贵。 反过来,连接会话初始化通常需要耗时的处理来执行用户认证,建立事务上下文以及建立后续数据库使用所需的会话的其他方面。

此外,数据库对所有连接会话的持续管理可能会对应用的可伸缩性施加主要限制因素。 基于并发连接会话的数量,诸如锁,内存,游标,事务日志,语句句柄和临时表之类的宝贵数据库资源都倾向于增加。

启用连接池允许池管理器在关闭连接后将其保留在“池”中。 下次需要连接时,如果请求的连接选项与池中的连接选项匹配,则将返回该连接,而不会产生与服务器建立另一个实际套接字连接的开销。

顺便说一句,您不需要为连接池管理器实现自己的逻辑。 您可以使用服务器上提供的某些功能。 例如:http://people.apache.org/~fhanik/jdbc-pool/jdbc-pool.html

2)语句池:从 JDBC 3.0 开始,JDBC 标准定义了一个语句缓存接口。 设置MaxPooledStatements连接选项将启用语句池。 启用语句池使驱动可以重新使用 Prepared Statement 对象。 关闭PreparedStatement后,它们将返回到池中而不是被释放,并且从池中检索下一个具有相同 SQL 语句的PreparedStatement,而不是针对服务器实例化和准备该语句。

语句缓存可以执行以下操作:

  1. 防止重复创建游标的开销
  2. 防止重复的语句解析和创建
  3. 重用客户端中的数据结构

请确保您的驱动支持此功能,并且默认情况下是否启用它。 如果您以编程方式进行操作,则示例代码可能是这样的。

Properties p = new Properties();
p.setProperty("user", "root");
p.setProperty("password", "password");
p.setProperty("MaxPooledStatements", "200");

connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/JDBCDemo", p);

在此处阅读有关语句池的更多信息:http://docs.oracle.com/cd/B28359_01/java.111/b31224/stmtcach.htm

同时考虑元数据性能

如果要在代码中处理元数据,那么这是需要注意的另一个重要方面。 这里的第一个技巧是使用尽可能多的参数(或过滤器)来获取指定的元数据。 例如,不要像这样调用getTables

DatabaseMetaData dbmd = connection.getMetaData();
ResultSet rs = dbmd.getTables(null,null,null,null);

在将请求发送到服务器时,至少指定模式将避免在每个表的所有表上返回信息:

DatabaseMetaData dbmd = connection.getMetaData();
ResultSet rs = dbmd.getTables(null,"testDB",null,null);

其次,请记住,大多数 JDBC 驱动在选择查询中返回所需数据时,会在提取时填充ResultSetMetaData对象。 使用此信息,而不是从DatabaseMetaData获取数据,这是附加请求,在大多数情况下是可以避免的。

selectStmt = connection.createStatement();
ResultSet rs = selectStmt.executeQuery("SELECT ID,FIRST_NAME,LAST_NAME,STAT_CD FROM EMPLOYEE WHERE ID <= 10");

ResultSetMetaData rsmd = rs.getMetaData();
rsmd.getColumnCount();
rsmd.getColumnName(0);
rsmd.getColumnType(0);
rsmd.getColumnTypeName(0);
rsmd.getColumnDisplaySize(0);
rsmd.getPrecision(0);
rsmd.getScale(0);

提示:可以考虑发出一个虚拟查询并使用返回的ResultSetMetaData来避免查询系统表,而不是使用getColumns获取有关表的数据!

仔细选择提交模式

在编写 JDBC 应用时,请确保考虑提交事务的频率。 每次提交都会导致驱动通过套接字发送数据包请求。 此外,数据库执行实际的提交,通常需要在服务器上进行磁盘 I/O。 考虑为您的应用删除自动提交模式,而改为使用手动提交以更好地控制提交逻辑。

使用的代码是:

Connection.setAutoCommit(false);

在网络流量上节省一些字节

为了减少网络流量,可以查看以下建议并对其进行调整以适合您的应用。

  1. 使用大容量客户端时,请使用addBatch()而不是使用PreparedStatement来插入数据。 这会在一个网络数据包中发送多个插入请求,并为您节省一些字节。
  2. 不要使用“SELECT * FROM TABLE。 而是指定实际需要的列名称。 我建议您将其作为一种习惯,因为我们很多时候都在这样做,却没有意识到它的负面影响。 试想一下,如果您在还要存储 BLOB 的表中执行此操作。 您从数据库中获取了如此重的对象,并且不使用它。 真是浪费
  3. 确保您的数据库设置了最大数据包大小,并且驱动与该数据包大小匹配。 为了获取更大的结果集,这减少了驱动和服务器之间发送/接收的总数据包数量。

这就是这篇文章的全部内容。 如果您有任何疑问,请发表评论。

祝您学习愉快!

Hiberate 教程

Hiberate 教程

原文: https://howtodoinjava.com/hibernate-tutorials/

Hibernate 是一个开源的 Java 持久化框架项目。 它使用 HQL 和 SQL 执行功能强大的对象关系映射和查询数据库。 Hibernate 是 Java 中 ORM 映射的出色工具。 它可以减少很多复杂性,从而减少应用中的缺陷,否则它们可能会找到存在的方法。 对于那些对 SQL 知识了解有限的开发人员来说,这特别有利。

Hibernate 最初是从 ORM 框架开始的,它已经分解为许多项目,例如 Hibernate SearchHibernate ValidatorHibernate OGM(用于 NoSQL 数据库),以此类推。

Hiberate 架构

下图总结了 Hiberate 架构中的主要构建块。

Hibernate Architecture

Hiberate 架构

让我们了解每个方块代表什么。

  1. 配置:通常以hibernate.propertieshibernate.cfg.xml文件编写。 对于 Java 配置,您可能会发现带有@Configuration注解的类。 会话工厂使用它与 Java 应用和数据库一起使用。 它代表了应用 Java 类型到 SQL 数据库的完整映射集。
  2. 会话工厂:任何用户应用都向会话工厂请求会话对象。 会话工厂使用上面列出的文件中的配置信息来适当地实例化会话对象。
  3. 会话:这表示应用和数据库在任何时间点之间的交互。 这由org.hibernate.Session类表示。 可以从SessionFactory bean 中检索会话的实例。
  4. 查询:它允许应用在数据库中查询一个或多个存储的对象。 Hibernate 提供了多种查询数据库的技术,包括NamedQueryCriteria API
  5. 一级缓存:它表示 Hibernate 会话对象与数据库交互时使用的默认缓存。 它也称为会话缓存,它在当前会话中缓存对象。 从会话对象到数据库的所有请求都必须通过一级缓存或会话缓存。 必须注意,会话对象处于活动状态之前,会话对象可以使用第一级缓存。
  6. 事务:使您能够实现数据一致性,并在出现意外情况时回滚。
  7. 持久化对象:这些是普通的旧 Java 对象(PO​​JO),它们通过 Hiberate 被持久化为数据库相关表中的行之一。它们可以在配置文件中配置(hibernate.cfg.xmlhibernate.properties)或使用@Entity注解进行注解。
  8. 二级缓存:用于跨会话存储对象。 需要明确启用此功能,并且需要为第二级缓存提供缓存供应器。 常见的二级缓存供应器之一是 EhCache

Hibernate 框架的主要功能

  • 对象/关系映射

    作为 ORM 框架,Hibernate 允许 Java 域对象与数据库表的映射,反之亦然。 结果,业务逻辑能够通过 Java 对象访问和操纵数据库实体。 它通过处理事务管理,自动主键生成,管理数据库连接和相关实现等方面,有助于加快整个开发过程。

  • JPA 供应器

    Hibernate 确实支持 Java 持久化 API(JPA)规范。 JPA 是一组用于在 Java 对象和关系数据库实体之间访问,持久化和管理数据的规范。

  • 惯用的持久化

    遵循面向对象原理的任何类(例如继承,多态等)都可以用作持久类。

  • 高性能和可扩展性

    Hibernate 支持各种技术,例如不同的获取策略,延迟初始化,乐观锁定等,以实现高性能,并且可以在任何环境中很好地扩展。

  • 易于维护

    Hibernate 不需要特殊的数据库表或字段,因此更易于维护。 它在系统初始化时生成 SQL。 与 JDBC 相比,它维护起来更快,更容易。

在此页面中,我已对该博客中所有可用的 Hiberate 示例进行了分类。 每次都会更新此页面,此博客中发布了一个新的 Hiberate 教程。 敬请关注 !!

随意建议您想阅读的主题。

HelloWorld 应用

Hibernate 3 简介和编写 HelloWorld 应用

在本文中,我将尝试详细介绍有关 hibernate 的更多信息,然后将确定在我们第一个运行的 java hibernate 示例应用中使用 hibernate 的基本步骤。

基本概念

如何在 Hiberate 4 中构建SessionFactory

如果您一直在观看以前的 Hiberate 版本,那么您一定已经注意到它们已经连续快速弃用了许多类。 不推荐使用的类是AnnotationConfigurationServiceRegistryBuilder,依此类推。

在这个 Hiberate 教程中,我给出了一个构建 Hiberate SessionFactory而不使用上面提到的不推荐使用的类的示例。 我正在使用最新的 Hiberate 版本,即Hibernate 4.3.6.Final,因此您可以确保使用最新的方法来构建会话工厂。

实体等价和等同概念

在我们的应用中,很多时候我们都面临着这样的情况,我们必须比较两个对象以检查它们的相等性以满足某些业务规则。 在核心 Java 中,我们已经掌握了检查对象是否相等的知识,但是在 Hiberate 下,我们还需要注意一些额外的事情。 让我们了解一下那些额外的概念。

定义 Hiberate 实体之间的关联映射

当我们使用 JPA 注解对 Java 类进行注解并使其成为持久化实体时,我们可能会遇到两个实体可以关联并且必须相互引用的情况,无论是单向还是双向。 在实际在 Hiberate 实体之间创建引用之前,让我们了解一些基本知识。

实体/持久化生命周期状态概念

给定一个映射到 Hibernate 的对象的实例,它可以处于四种不同状态中的任何一种:瞬态,持久,分离或删除。 我们今天将在这个 Hiberate 教程中学习它们。

将内存数据库与 Hiberate 一起使用

在此示例中,我使用 HSQLDB 数据库通过我们的 Hiberate 代码创建和访问内存数据库。

Hiberate JPA 级联类型

为了启用级联和反向效果,我们在实体中使用了“CascadeType”属性。 在本教程中,我们将学习通过CascadeType进行级联的各种可用选项。

Hiberate 注解与映射的优缺点

您可能知道,在内联注解之前,创建 Hiberate 映射的唯一方法是通过 XML 文件。 尽管来自 Hibernate 和第三方项目的各种工具允许部分或全部映射从 Java 源代码自动生成。 今天,注解是定义映射的最新方法,但并不是自动的最佳方法。 让我们先讨论 Hiberate(或我应该说 JPA)注解的缺点和好处,然后再讨论何时应用它们。

Hiberate 查询语言(HQL)

HQL 是一种类似于 SQL 的面向对象的查询语言,但是 HQL 不会对表和列进行操作,而是处理持久化对象及其属性。 它是 JPQL(Java 持久化查询语言)的超集; JPQL 查询是有效的 HQL 查询,但并非所有 HQL 查询都是有效的 JPQL 查询。 HQL 是一种具有自己的语法和语法的语言。

让我们使用以下示例学习 HQL:

  1. 基本 HQL 语法
    • 更新操作
    • 删除操作
    • 插入操作
    • 选择操作
  2. from子句和别名
  3. select子句和投影
  4. 使用命名参数
  5. 分页结果集
  6. 获得独特的结果
  7. 使用order by子句对结果进行排序
  8. 关联
  9. 汇总方法
  10. 命名查询
  11. 使用本机 SQL
  12. 启用日志记录和注解

Hiberate 条件查询

条件查询 API 使您可以用 Java 构建嵌套的结构化查询表达式,从而提供了编译时语法检查,而这是使用 HQL 或 SQL 这样的查询语言无法实现的。 条件查询 API 还包括示例查询(QBE)功能。 这样,您就可以提供示例对象,这些对象包含要检索的属性,而不必逐步说明查询的组件。 它还包括投影和聚合方法,包括count()。 让我们详细探讨它的不同功能。

  1. 基本用法示例
  2. 对条件使用限制
  3. 分页结果集
  4. 获得独特的结果
  5. 获得不同的结果
  6. 对查询结果进行排序
  7. 执行关联(连接)
  8. 添加投影
  9. 示例查询(QBE)
  10. 总结

Hiberate 中的延迟加载

在本教程中,我将讨论 Hiberate 中必须被称为延迟加载的功能。 如果您在大型应用中工作,这特别有用。

CRUD 操作实例

Hiberate 插入查询教程

在本教程中,我给出了在单个表中插入数据的示例。

Hiberate 命名查询教程

Hiberate 中的命名查询是一种将 HQL 语句分组在单个位置的技术,并在以后需要使用它们时以某种名称对其进行引用。 由于这些 HQL 语句不再分散在整个代码中,因此在很大程度上有助于代码清除。

从数据库中加载实体的示例

使用loadget方法加载 Hiberate 实体的示例。

用于保存实体的Save()saveOrUpdate()

请不要创建使用 Hiberate 注解映射的类的实例不会自动将对象持久保存到数据库中。 将其附加到有效的 Hiberate 会话后,必须显式保存它。 让我们学习如何做。

合并和刷新实体

在本教程中,我将讨论有关 Hiberate 会话类中存在的refresh()merge()方法的一些想法。

Hiberate 实体映射

使用注解的 Hiberate 一对一映射

让我们讨论一下 hibernate 支持的一对一映射的变体:

  1. 使用外键关联
  2. 使用通用连接表
  3. 使用共享主键

使用注解的 Hiberate 一对多映射

讨论 Hiberate 中支持的一对多映射的变体:

  1. 使用外键关联
  2. 使用连接表

使用注解的 Hiberate 多对多映射

讨论 Hibernate 中支持的多对多映射的变体。

Hiberate 连接池和缓存

C3P0 连接池配置教程

默认情况下,Hibernate 使用 JDBC 连接以便与数据库进行交互。 创建这些连接非常昂贵 - Hibernate 在典型的使用情况下可能会执行最昂贵的单个操作。 由于 JDBC 连接管理非常昂贵,因此您可能会建议您使用连接池,它可以提前打开连接(并仅在需要时关闭它们,而不是“不再使用时”)。

C3P0 是外部连接池的示例。 在本教程中,我们将学习如何将其与 hibernate 一起使用。

Hiberate EhCache 配置

缓存是 ORM 框架提供的功能,可帮助用户获得快速运行的 Web 应用,同时帮助框架本身减少在单个事务中对数据库的查询数量。 Hibernate 还在第一级和第二级提供此缓存功能。

在本教程中,我将举一个使用 ehcache 配置作为 hibernate 中的二级缓存的示例。

Hiberate 一级缓存示例

默认情况下,Hiberate 下的第一级缓存已启用,您无需执行任何操作即可使此功能正常工作。 让我们进一步了解它。

Hiberate 二级缓存示例

在本教程中,我将提供有关 Hiberate 二级缓存的概念,并提供使用代码段的示例。

Hiberate 最佳实践

Hiberate @NaturalId用法和示例

Hibernate 4 带来了许多改进,而@NaturalId就是这样很好的改进之一。 如您所知,@Id注解用作指定实体主键的元数据。 但是有时,实体通常在 DAO 层代码中使用 ID,该 ID 不是主键,而是其逻辑或自然 ID。 在这种情况下,@NaturalId注解将证明是 Hiberate 下命名查询的良好替代。

获取延迟加载的实体引用

延迟加载是计算机编程中常用的一种设计模式,用于将对象的初始化推迟到需要它的时间点。 可以通过在 Hiberate 映射注解中指定“fetch = FetchType.LAZY”来完成 Hiberate 延迟加载。

与其他框架的整合

将 Hibernate 与 Spring 框架集成

本 Hiberate 教程侧重于将 Spring 3 框架与 Hibernate 结合使用。 我将展示这种集成的结果,一个基本的端到端应用流看起来如何。

您可能会遇到的异常

[已解决]初始SessionFactory创建失败。org.hibernate.HibernateException:命名查询中的错误

Hibernate 教程参考

Hiberate 文档

Hibernate 示例 – HelloWorld 示例逐步简介

原文: https://howtodoinjava.com/hibernate/hibernate-3-introduction-and-writing-hello-world-application/

Hibernate 由 Gavin King 在 2001 年启动,它是使用 EJB2 样式实体 bean 的替代方法。 当时,它的任务是通过简化复杂性并允许缺少功能来为 EJB2 提供更好的持久化。

Hibernate 使用其映射文件和配置文件来实现其目标。 随着 JDK 1.5 在 Java 社区中引入注解,Hibernate 社区开始致力于支持注解的 Hibernate 3。 Hiberate 的当前版本是Hiberate 5

在带有注解的 Hiberate 示例中,我们将学习有关 Hiberate 的更多信息,并逐步为初学者构建第一个正在运行的示例应用。

Table of Contents

What is hibernate
How hibernate works
Relation of hibernate with JPA
Hibernate HelloWorld example

什么是 Hibernate

Hibernate 是 Java 的开源对象关系映射工具。 它提供了一个框架,用于将面向对象的域模型映射到传统的关系数据库。 Hibernate 不仅负责从 Java 类到数据库表(以及从 Java 数据类型到 SQL 数据类型)的映射,而且还提供数据查询和检索功能,并且可以显着减少开发时间,否则将花费大量时间在 SQL 和 JDBC 中进行手动数据处理 。

通过配置 XML 文件或使用 Java 注解将 Java 类映射到数据库表。 提供了用于安排类之间的一对多多对多关系的设施。

除了管理对象之间的关联之外,Hibernate 还可以管理自反关联,其中对象与自己类型的其他实例具有一对多关系。

Hibernate 如何运作

Hiberate 不会妨碍我们; 也不会迫使我们改变对象的行为方式。 这些对象无需实现任何神奇的接口即可拥有持久的能力。 我们要做的全部工作是将一些元数据放入注解的形式,这些注解告诉 hibernate 在与数据库映射时如何使用它们。 在运行时,hibernate 读取这些注解并使用此信息来构建查询以发送到某些关系数据库。

Hibernate 中有一个简单直观的 API,可以对数据库表示的对象执行查询,以更改我们通常在程序中与它们正常交互的那些对象,然后告诉 Hibernate 保存更改。 创建新对象也同样简单。 我们只是以常规方式创建它们,并使用注解将它们告知 Hibernate,以便它们可以存储到数据库中。

Hiberate 与 JPA 的关系

JPA(Java 持久化 API)是持久化供应器要实现的规范。 Hibernate 就是这样的 JPA 规范的实现。 我们可以使用 JPA 注解对注解进行尽可能多的注解,但是如果没有实现,将不会发生任何事情。

可以将 JPA 视为必须遵循的准则/规范或接口,而 Hibernate JPA 实现是符合 JPA 定义的 API 并提供底层功能的代码。

当我们将 Hibernate 与 JPA 一起使用时,实际上是在使用 Hibernate JPA 实现。 这样做的好处是我们可以将 JPA 的 Hiberate 实现换成 JPA 规范的另一种实现。

当我们使用直接 Hiberate 时,由于其他 ORM 可能使用不同的方法/配置和注解,因此您将锁定到实现中,因此我们不能仅切换到另一个 ORM。

Java Hibernate HelloWorld 示例

让我们逐步创建一个 Hiberate 的 HelloWorld 示例。 在此示例中,我创建了一个Employee类并声明了四个属性idemailfirstnamelastname

我希望id属性应自动生成,以便应用代码不存储员工 ID 的本地缓存。

到目前为止,我们的目标是要在第一个应用中进行制作。 让我们确定需要创建的文件。

  1. hibernate.cfg.xml - 此配置文件将用于存储数据库连接信息和架构级别设置。
  2. EmployeeEntity.java - 此类将引用具有 Hiberate 注解的 Java POJO。
  3. HibernateUtil.java – 此类将具有工具方法,这些方法将用于创建会话工厂和会话对象。
  4. TestHibernate.java - 此类将用于测试我们的配置设置和Emplyee实体注解。

在进入代码之前,让我们看一下项目的设置并添加 maven 依赖项,这些依赖项需要添加到pom.xml以包括所有编译时间和运行时依赖项。

1. 创建一个 Maven 项目

mvn-create-java-project

2. 使项目支持 Eclipse

Adding-eclipse-support in maven project

3. 将 Java 项目导入到 Eclipse 工作区

Import java project in eclipse

以上步骤将创建最小设置。 现在该添加 Hiberate 依赖项了。

4. Hiberate Maven 依赖项

<dependency>
	<groupid>org.hibernate</groupid>
	<artifactid>hibernate-commons-annotations</artifactid>
	<version>3.0.0.ga</version>
</dependency>
<dependency>
	<groupid>org.hibernate</groupid>
	<artifactid>hibernate-annotations</artifactid>
	<version>3.3.0.ga</version>
</dependency>
<dependency>
	<groupid>mysql</groupid>
	<artifactid>mysql-connector-java</artifactid>
	<version>5.1.6</version>
</dependency>
<dependency>
	<groupid>antlr</groupid>
	<artifactid>antlr</artifactid>
	<version>2.7.6</version>
</dependency>
<dependency>
	<groupid>commons-collections</groupid>
	<artifactid>commons-collections</artifactid>
	<version>3.1</version>
</dependency>
<dependency>
	<groupid>dom4j</groupid>
	<artifactid>dom4j</artifactid>
	<version>1.6.1</version>
</dependency>
<dependency>
	<groupid>javassist</groupid>
	<artifactid>javassist</artifactid>
	<version>3.4.GA</version>
</dependency>
<dependency>
	<groupid>javax.transaction</groupid>
	<artifactid>jta</artifactid>
	<version>1.1</version>
</dependency>
<dependency>
	<groupid>org.slf4j</groupid>
	<artifactid>slf4j-api</artifactid>
	<version>1.5.6</version>
</dependency>
<dependency>
	<groupid>org.slf4j</groupid>
	<artifactid>slf4j-log4j12</artifactid>
	<version>1.5.6</version>
</dependency>

请注意,在此 Hiberate 示例中,我们并没有使用所有的 Maven 依赖项,但是当我们开始扩展应用时将使用它们。

5. Hiberate 配置

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernatetest</property>
        <property name="hibernate.connection.password">lg225295</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">true</property>
		<property name="hbm2ddl.auto">update</property>
        <mapping class="hibernate.test.dto.EmployeeEntity"></mapping>
    </session-factory>
</hibernate-configuration>

运行此 Hiberate 示例之前,请不要忘记设置正确的密码。

6. Hiberate 实体类

package hibernate.test.dto;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

import org.hibernate.annotations.OptimisticLockType;

@Entity
@org.hibernate.annotations.Entity(optimisticLock = OptimisticLockType.ALL)
@Table(name = "Employee", uniqueConstraints = {
		@UniqueConstraint(columnNames = "ID"),
		@UniqueConstraint(columnNames = "EMAIL") })
public class EmployeeEntity implements Serializable {

	private static final long serialVersionUID = -1798070786993154676L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	private Integer employeeId;

	@Column(name = "EMAIL", unique = true, nullable = false, length = 100)
	private String email;

	@Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
	private String firstName;

	@Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
	private String lastName;

	// Accessors and mutators for all four fields
}

7. Hiberate 会话工厂

package hibernate.test;

import java.io.File;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;

public class HibernateUtil 
{
	private static final SessionFactory sessionFactory = buildSessionFactory();

	private static SessionFactory buildSessionFactory() 
	{
		try {
			// Create the SessionFactory from hibernate.cfg.xml
			return new AnnotationConfiguration().configure(
					new File("hibernate.cgf.xml")).buildSessionFactory();

		} catch (Throwable ex) {
			// Make sure you log the exception, as it might be swallowed
			System.err.println("Initial SessionFactory creation failed." + ex);
			throw new ExceptionInInitializerError(ex);
		}
	}

	public static SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	public static void shutdown() {
		// Close caches and connection pools
		getSessionFactory().close();
	}
}

请不要忘记使用hibernate.cgf.xml的正确路径。

8. 演示

package hibernate.test;

import hibernate.test.dto.EmployeeEntity;
import org.hibernate.Session;

public class TestHibernate {

	public static void main(String[] args) {
		Session session = HibernateUtil.getSessionFactory().openSession();
		session.beginTransaction();

		// Add new Employee object
		EmployeeEntity emp = new EmployeeEntity();
		emp.setEmail("[email protected]");
		emp.setFirstName("demo");
		emp.setLastName("user");

		session.save(emp);

		session.getTransaction().commit();
		HibernateUtil.shutdown();
	}
}

上面的代码将在数据库中创建一个新的表Employee,并在此表中插入一行。 在日志中,您可以验证已执行的插入语句。

Hibernate: insert into Employee (EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?)

如果您在运行 Hiberate 的 HelloWorld 示例时遇到问题,请给我评论,我将很高兴与您讨论问题。

学习愉快!

下载源码

参考文献:

Hiberate 首页

Hibernate 获取实体示例 – getload方法

原文: https://howtodoinjava.com/hibernate/how-to-load-get-entity-in-hibernate/

学习使用session.load()session.get()方法通过 ID 来获取 Hiberate 实体。 了解getload方法之间的区别,该方法通过 ID 从数据库中获取实体。

1. Hiberate 加载实体 – session.load()

Hibernate 的Session接口提供了几种重载的load()方法,用于从数据库中加载实体。 每个load()方法都需要对象的主键作为标识符,并且必须提供该主键。

除了 ID,hibernate 还需要知道使用哪个类或实体名称来查找具有该 ID 的对象。 load()方法返回后,我们需要将返回的对象强制转换为合适的类类型,以进一步使用它。 我们需要load()方法来正常工作。

1.1 Session.load()方法

让我们看看 Hiberate 会话界面中可用的load()方法的不同风格。

public Object load(Class theClass, Serializable id) throws HibernateException
public Object load(String entityName, Serializable id) throws HibernateException
public void load(Object object, Serializable id) throws HibernateException

  1. 第一种方法需要您要加载的类类型以及唯一的 ID。
  2. 第二种方法直接要求实体名称和唯一 ID。 两种方法都将填充的实体对象作为返回值返回,并将其转换为所需的类型。
  3. 第三,以对象为参数。 该对象应与要加载的对象属于同一类,并且应为空。 Hibernate 将使用您请求的对象填充该对象。

通过 Hiberate 会话可用的其他load()方法也将锁定模式作为参数。 锁定模式指定 Hibernate 是否应调查对象的高速缓存,以及 Hibernate 应对表示该对象的数据行使用哪个数据库锁定级别。

在官方文档中,Hiberate 开发人员声称 Hibernate 通常会为我们选择正确的锁定模式,尽管在某些情况下手动选择正确的锁定很重要。

当我们完成基本的 Hiberate 概念时,我们将讨论有关锁的更多信息。

1.2 Session.load()方法示例

让我们以最简单的形式看一下每种加载方法的示例,以清楚地了解我们上面所读的内容。

public class TestHibernate
{
   public static void main(String[] args)
   {
      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      // Create new Employee object
      EmployeeEntity emp = new EmployeeEntity();
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");

      //Save employee
      sessionOne.save(emp);
      //store the employee id generated for future use
      Integer empId = emp.getEmployeeId();
      sessionOne.getTransaction().commit();

      /************************************************************************/

      //Let's open a new session to test load() methods
      Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
      sessionTwo.beginTransaction();

      //first load() method example
      EmployeeEntity emp1 = (EmployeeEntity) sessionTwo.load(EmployeeEntity.class, empId);
      System.out.println(emp1.getFirstName() + " - " +emp1.getLastName());

      //Let's verify the entity name
      System.out.println(sessionTwo.getEntityName(emp1));

      sessionTwo.getTransaction().commit();

      /************************************************************************/

      Session sessionThree = HibernateUtil.getSessionFactory().openSession();
      sessionThree.beginTransaction();

      //second load() method example
      EmployeeEntity emp2 = (EmployeeEntity) sessionThree.load("com.howtodoinjava.demo.entity.EmployeeEntity", empId);
      System.out.println(emp2.getFirstName() + " - " +emp2.getLastName());

      sessionThree.getTransaction().commit();

      /************************************************************************/

      Session sessionFour = HibernateUtil.getSessionFactory().openSession();
      sessionFour.beginTransaction();

      //third load() method example
      EmployeeEntity emp3 = new EmployeeEntity();
      sessionFour.load(emp3 , empId);
      System.out.println(emp3.getFirstName() + " - " +emp3.getLastName());

      sessionFour.getTransaction().commit();

      HibernateUtil.shutdown();
   }    
}

程序输出。

Hibernate: insert into Employee (FIRST_NAME, LAST_NAME, ID) values (?, ?, ?)
Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_, 
			employeeen0_.LAST_NAME as LAST_NAM3_1_0_ 
			from Employee employeeen0_ where employeeen0_.ID=?

Lokesh - Gupta		//First load method

com.howtodoinjava.demo.entity.EmployeeEntity
Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_, 
			employeeen0_.LAST_NAME as LAST_NAM3_1_0_ 
			from Employee employeeen0_ where employeeen0_.ID=?

Lokesh - Gupta		//Second load method

Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_, 
			employeeen0_.LAST_NAME as LAST_NAM3_1_0_ 
			from Employee employeeen0_ where employeeen0_.ID=?

Lokesh - Gupta		//Third load method

因此,我们能够从所有三种加载方法中成功加载实体。 现在转到get()方法。

2. Hiberate 通过 ID 获取实体 – session.get()

get()方法与load()方法非常相似。 get()方法采用标识符以及实体名称或类。 还有两种get()方法以锁定模式作为参数,但是稍后我们将讨论锁定模式。 其余get()方法如下:

public Object get(Class clazz, Serializable id) throws HibernateException
public Object get(String entityName, Serializable id) throws HibernateException

使用load()get()方法时代码没有太大差异,您需要做的只是在前两个示例中将load()方法替换为get()方法。 没有get()等同于最后一种load()方法。

您可以修改上面的示例并测试代码。 如果您有任何问题,请告诉我。

3. load()get()方法之间的区别

为什么我们有两种方法可以完成相同的工作。 实际上,这也是面试问题经常问到的问题。

获取和加载方法之间的区别在于,当标识符在数据库中不存在时,返回值将返回。

  1. get()方法的情况下,如果没有标识符,我们将获得返回值NULL
  2. 但是在load()方法的情况下,我们将获得一个运行时异常。

加载方法的异常如下所示:

Exception in thread "main" org.hibernate.ObjectNotFoundException: No row with the given identifier exists: 
[com.howtodoinjava.demo.entity.EmployeeEntity#23]
at org.hibernate.internal.SessionFactoryImpl$1$1.handleEntityNotFound(SessionFactoryImpl.java:253)
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:275)
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:151)
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1070)
	at org.hibernate.internal.SessionImpl.load(SessionImpl.java:940)

这就是本入门级教程的全部内容,讨论了加载 Hiberate 实体的过程。

学习愉快!

Hibernate 插入查询教程

原文: https://howtodoinjava.com/hibernate/hibernate-insert-query-tutorial/

Hibernate 是 Java 语言的对象关系映射(ORM)库,它提供了将面向对象的域模型映射到传统关系数据库的框架。 这意味着不需要构建和执行与数据库交互的 SQL 查询。 您只需要通过调用 Hiberate API 来指示 Hiberate,Hiberate 将代表您创建并执行这些 SQL 查询。

在这个 Hiberate 教程中,我给出了在单个表中插入数据的示例。 我们在应用中有一个对象,即Employee。 我们想将其存储在数据库中。 Employee类具有以下属性:

  • 员工编号 – 整数
  • 电子邮件 – 字符串
  • 名 – 字符串
  • 姓 – 字符串

它可以有更多的字段,但是我仅用四个来举例说明。

1. Hiberate 实体类

Hiberate 与标记为 Hiberate 实体的 Java POJO 类进行对话。 要将 Java 类转换为实体,我们需要在其上应用@Entity注解。 还有其他 Hiberate 注解,例如@Table@Column@Id等,它们有助于将实体字段映射到数据库表和列 。

应用注解后,Employee实体类如下所示。

@Entity
@Table(name = "EMPLOYEE", uniqueConstraints = {
		@UniqueConstraint(columnNames = "ID"),
		@UniqueConstraint(columnNames = "EMAIL") })
public class EmployeeEntity implements Serializable 
{	
	private static final long serialVersionUID = -1798070786993154676L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	private Integer id;

	@Column(name = "EMAIL", unique = true, nullable = false, length = 100)
	private String email;

	@Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
	private String firstName;

	@Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
	private String lastName;

	//Getters and setters
}

2. Hiberate 配置

下一步是在“hibernate.cgf.xml”文件中配置 Hiberate。 该文件包含系统和数据库连接元数据中的所有可用实体。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC 
"-//Hibernate/Hibernate Configuration DTD 3.0//EN" 
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernatedemo</property>
        <property name="hibernate.connection.password">password</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">true</property>
		<property name="hbm2ddl.auto">create</property>
        <mapping class="hibernate.test.dto.EmployeeEntity"></mapping>
    </session-factory>
</hibernate-configuration>

3. Hibernate SessionFactory

现在,Hiberate 配置已经就绪,我们必须构建 Hiberate 会话工厂。 会话工厂用于获取数据库以及各种活动(如提交和回滚)的连接。

public class HibernateUtil 
{
	private static final SessionFactory sessionFactory = buildSessionFactory();

    private static SessionFactory buildSessionFactory() 
    {
        try 
        {
            // Create the SessionFactory from hibernate.cfg.xml
            return new AnnotationConfiguration().configure(new File("hibernate.cgf.xml")).buildSessionFactory();
        }
        catch (Throwable ex) {
            // Make sure you log the exception, as it might be swallowed
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public static void shutdown() {
    	// Close caches and connection pools
    	getSessionFactory().close();
    }
}

4. Hibernate 插入查询示例

最后,我们将使用此 Hiberate 会话工厂执行插入查询以将员工保存在数据库中。

public class TestHibernateInsert {

	public static void main(String[] args) 
	{
		Session session = HibernateUtil.getSessionFactory().openSession();
		session.beginTransaction();

		//Add new Employee object
		EmployeeEntity emp = new EmployeeEntity();
		emp.setEmail("[email protected]");
		emp.setFirstName("lokesh");
		emp.setLastName("gupta");

		//Save the employee in database
		session.save(emp);

		//Commit the transaction
		session.getTransaction().commit();
		HibernateUtil.shutdown();
	}
}

程序日志。

Hibernate: insert into EMPLOYEE (EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?)

让我们验证数据库中的数据。

Hibernate insert query example

Hibernate 插入查询示例

5. 故障排除

  1. 请确保在项目中添加 Hiberate 和 mysql 驱动依赖项。 在 Maven 中,这些如下:

    <dependency>
    	<groupId>org.hibernate</groupId>
    	<artifactId>hibernate-commons-annotations</artifactId>
    	<version>3.0.0.ga</version>
    </dependency>
    <dependency>
    	<groupId>org.hibernate</groupId>
    	<artifactId>hibernate-annotations</artifactId>
    	<version>3.3.0.ga</version>
    </dependency>
    <dependency>
    	<groupId>mysql</groupId>
    	<artifactId>mysql-connector-java</artifactId>
    	<version>5.1.6</version>
    </dependency>
    
    
  2. 验证hibernate.cgf.xml文件存在并且具有有效的数据库元数据。

下载源码

学习愉快!

Hiberate 合并和刷新实体

原文: https://howtodoinjava.com/hibernate/merging-and-refreshing-hibernate-entities/

在先前的教程中,我们了解了在 Hiberate 中使用save()saveOrUpdate()方法保存实体的知识。 从中我们了解到,在大多数情况下,您甚至都不需要那些方法,因为 hibernate 自动管理持久实体上的更新。 在大多数情况下,您只需要关心瞬态对象。 在本教程中,我将讨论有关 Hiberate 会话类中存在的refresh()merge()方法的一些想法。

使用refresh()方法刷新 Hiberate 实体

有时,我们会遇到这样的情况:我们的应用数据库被某些外部应用/代理修改,因此您的应用中的相应 Hiberate 实体实际上与其数据库表示不同步,即具有旧数据。 在这种情况下,可以使用session.refresh()方法用数据库中可用的最新数据重新填充实体。

您可以在Session接口上使用refresh()方法之一来刷新持久对象的实例,如下所示:

public void refresh(Object object) throws HibernateException
public void refresh(Object object, LockMode lockMode) throws HibernateException

这些方法将从数据库重新加载对象的属性,并覆盖它们。 在现实生活中的应用中,除了上述情况之外,您不必经常使用refresh()方法。

我们来看一个refresh()方法的示例。

public class RefreshEntityExample
{
   public static void main(String[] args)
   {
      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      //Create new Employee object
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");

      //Save employee
      sessionOne.save(emp);
      sessionOne.getTransaction().commit();
      sessionOne.close();

      //Verify employee's firstname
      System.out.println(verifyEmployeeFirstName(1, "Lokesh"));

      Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
      sessionTwo.beginTransaction();

      //This 
      emp.setFirstName("Vikas");
      sessionTwo.refresh(emp);

      sessionTwo.getTransaction().commit();
      sessionTwo.close();

      System.out.println(emp.getFirstName().equals("Lokesh"));

      HibernateUtil.shutdown();
   }  

   private static boolean verifyEmployeeFirstName(Integer employeeId, String firstName){
      Session session = HibernateUtil.getSessionFactory().openSession();
      EmployeeEntity employee = (EmployeeEntity) session.load(EmployeeEntity.class, employeeId);
      //Verify first name
      boolean result = firstName.equals(employee.getFirstName());
      session.close();
      //Return verification result
      return result;
   }
}

Output:

true
true

查看突出显示的线条上方。

  • 第 15 行以“Lokesh”的名字保存员工
  • 第 26 行设置了名字“Vikas”。 由于实体已分离,因此不会更新数据库。
  • 第 27 行使用refresh()方法用数据库刷新实体。
  • 第 32 行验证在实体中设置的名字是否已用“Lokesh”更新,因为这是数据库当前所拥有的名字。

这全部与刷新方法有关。 让我们看另一种类似的方法merge()

使用merge()方法合并 Hiberate 实体

方法merge()refresh()的作用完全相反,即,它使用来自分离实体的值更新数据库。 刷新方法是使用最新的数据库信息更新实体。 所以基本上,两者是完全相反的。

当您希望将分离实体再次更改为持久状态时执行合并,并且将分离实体的更改迁移到(或覆盖)数据库。 合并操作的方法签名为:

Object merge(Object object)
Object merge(String entityName, Object object)

Hibernate 官方文档对merge()方法给出了很好的解释:

将给定对象的状态复制到具有相同标识符的持久对象上。 如果当前没有与该会话关联的持久化实例,则将其加载。 返回持久实例。 如果给定实例未保存,请保存的副本并将其作为新的持久实例返回。 给定的实例不与会话关联。 如果关联是用cascade="merge"映射的,则此操作将级联到关联的实例。

因此,如果我以下面的代码为例,那么下面列出的几点对您来说应该很清楚。

EmployeeEntity mergedEmpEntity = session.merge(empEntity);

  • empEntity传递给merge()方法时,它是分离的实体。
  • merge()方法将使用从empEntity获取的标识符信息搜索已加载的EmployeeEntity实例。 如果找到了这样的持久化实体,那么它将用于更新。 否则,将使用empEntity中存在的相同标识符信息将新的EmployeeEntity加载到会话中。
  • 数据从empEntity复制到新发现/加载的实体。
  • 由于新实体/发现实体是永久性的,因此从empEntity复制到该实体的所有数据都会自动保存到数据库中。
  • merge()方法返回对该新实体的引用,并将其分配给mergedEmpEntity变量。
  • empEntity仍然是独立实体。
public class MergeEntityExample
{
   public static void main(String[] args)
   {
      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      //Create new Employee object
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");

      //Save employee
      sessionOne.save(emp);
      sessionOne.getTransaction().commit();
      sessionOne.close();

      //Verify employee's firstname
      System.out.println(verifyEmployeeFirstName(1, "Lokesh"));

      Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
      sessionTwo.beginTransaction();

      //Set new first name
      emp.setFirstName("Vikas");

      //Merge the emp object using merge() method
      EmployeeEntity mergedPersistentEmpEntity = (EmployeeEntity) sessionTwo.merge(emp);

      sessionTwo.getTransaction().commit();
      sessionTwo.close();

      //Verify employee's firstname again in database
      System.out.println(verifyEmployeeFirstName(1, "Vikas"));

      HibernateUtil.shutdown();
   }  

   private static boolean verifyEmployeeFirstName(Integer employeeId, String firstName){
      Session session = HibernateUtil.getSessionFactory().openSession();
      EmployeeEntity employee = (EmployeeEntity) session.load(EmployeeEntity.class, employeeId);
      //Verify first name
      boolean result = firstName.equals(employee.getFirstName());
      session.close();
      //Return verification result
      return result;
   }
}

Output:

true
true

在上面的示例中,mergedPersistentEmpEntity是具有持久化的新实体。 因此,如果您想进行其他更改,请输入mergedPersistentEmpEntity实例。

本教程涵盖了 Hiberate 中的merge()refresh()方法。 请记住,可以问以下问题:merge()refresh()之间的差异,或merge()load()之间的差异等。准备好在面试问题中遇到这些差异。

祝您学习愉快!

Hibernate 4 – 获取延迟加载的实体引用

原文: https://howtodoinjava.com/hibernate/hibernate-4-example-to-get-entity-reference-for-lazy-loading/

根据 Wikipedia 的定义, 延迟加载是一种设计模式,通常用于计算机编程中,以将对象的初始化推迟到需要的时间点。 我们知道,在 Hiberate 映射中,可以通过在 Hiberate 映射注解中指定fetch = FetchType.LAZY来完成延迟加载。 例如:

@ManyToOne ( fetch = FetchType.LAZY )
@JoinColumns( {
		@JoinColumn(name="fname", referencedColumnName = "firstname"),
		@JoinColumn(name="lname", referencedColumnName = "lastname")
		} )
public EmployeeEntity getEmployee() {
	return employee;
}

关键是仅在定义两个实体之间的映射时才应用它。 如果已经在DepartmentEntity中定义了以上实体,则如果您获取DepartmentEntity,则EmployeeEntity将被延迟加载。

但是,如果您要延迟加载DepartmentEntity本身,即主实体本身应延迟加载,该怎么办。

可以通过在IdentifierLoadAccess类中使用getReference()方法解决此问题。

让我们通过此示例了解用法。

作为参考,最新的 Hiberate Maven 依赖项如下:

<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-core</artifactId>
	<version>4.3.0.Beta3</version>
</dependency>

现在,我有一个主实体类EmployeeEntity,它可以具有多个属性以及与其他实体的映射。

EmployeeEntity.java

@Entity
@Table(name = "Employee", uniqueConstraints = {
		@UniqueConstraint(columnNames = "ID"),
		@UniqueConstraint(columnNames = "EMAIL") })
public class EmployeeEntity implements Serializable {

	private static final long serialVersionUID = -1798070786993154676L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	private Integer employeeId;

	//Use the natural id annotation here
	@NaturalId (mutable = false)
	@Column(name = "EMAIL", unique = true, nullable = false, length = 100)
	private String email;

	@Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
	private String firstName;

	@Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
	private String lastName;

	//Setters and Getters
}

我想在我的代码中延迟加载的主实体之上延迟加载,即我可以在一个地方获取实体的引用,但实际上可能需要在另一个地方进行引用。 仅在需要时,我才想初始化或加载其数据。 到时候为止,我只想参考。

让我们在代码示例中这样做:

TestHibernate.java

public class TestHibernate
{
	public static void main(String[] args) 
	{
		Session session = HibernateUtil.getSessionFactory().openSession();
		session.beginTransaction();

		//Add new Employee object
		EmployeeEntity emp = new EmployeeEntity();
		emp.setEmail("[email protected]");
		emp.setFirstName("demo");
		emp.setLastName("user");
		//Save entity
		session.save(emp);

		session.getTransaction().commit();
		session.close();

		session = HibernateUtil.getSessionFactory().openSession();
		session.beginTransaction();

		//Get only the reference of EmployeeEntity for now
		EmployeeEntity empGet = (EmployeeEntity) session.byId( EmployeeEntity.class ).getReference( 1 );

		System.out.println("No data initialized till now; Lets fetch some data..");

		//Now EmployeeEntity will be loaded from database when we need it
		System.out.println(empGet.getFirstName());
		System.out.println(empGet.getLastName());

		session.getTransaction().commit();
		HibernateUtil.shutdown();
	}
}

Output in console:

Hibernate: insert into Employee (EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?)

No data initialized till now; Lets fetch some data..

Hibernate: select employeeen0_.ID as ID1_0_0_, employeeen0_.EMAIL as EMAIL2_0_0_, employeeen0_.FIRST_NAME as FIRST3_0_0_, 
employeeen0_.LAST_NAME as LAST4_0_0_ from Employee employeeen0_ where employeeen0_.ID=?

demo
user

要下载以上教程的源代码,请单击下面的下载链接。

祝您学习愉快!

Maven 强制最低 Java 版本

原文: https://howtodoinjava.com/maven/how-to-enforce-operating-system-and-minimum-java-version-using-maven-enforcer-plugin/

很多时候,如果部署环境没有特定的操作系统,或者它不包含最低要求的 Java 版本,我们需要强制 Java 构建过程应立即停止。 如果您正在使用 maven 进行构建,则可以使用 maven 强制执行器插件轻松配置这些限制。

强制执行器插件提供了控制某些环境约束的目标,例如 Maven 版本,JDK 版本和 OS 系列以及更多标准规则和用户创建的规则。

阅读更多:最新规则集

让我们通过一个例子来看看如何使用这个插件:

1)创建 Maven 项目

$ mvn archetype:generate 
		-DgroupId=com.howtodoinjava 
		-DartifactId=EnforceJavaVersionDemo
		-DarchetypeArtifactId=maven-archetype-quickstart 
		-DinteractiveMode=false

2)添加 Maven 执行器插件

<build>
	<plugins>
		<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-enforcer-plugin</artifactId>
		<version>3.0.0-M2</version>
		<executions>
			<execution>
			<id>enforce-versions</id>
			<goals>
				<goal>enforce</goal>
			</goals>
			<configuration>
				<rules>
					<requireMavenVersion>
						<version>2.0.6</version>
					</requireMavenVersion>
					<requireJavaVersion>
						<version>1.5</version>
					</requireJavaVersion>
					<requireOs>
						<family>unix</family>
					</requireOs>
				</rules>
			</configuration>
			</execution>
		</executions>
		</plugin>
	</plugins>
</build>

在这里,所需的依赖项是操作系统是 linux,maven 版本是 2.0.6,而 Java 版本是 1.5。 我在 Windows 机器上运行此代码,因此,上面的pom.xml文件应兼容操作系统。

3)演示

版本不匹配的项目进行编译。

$ mvn compile

编译失败,并出现以下错误。

[INFO] --- maven-enforcer-plugin:1.2:enforce (enforce-versions) @ EnforceJavaVer
sionDemo ---
[WARNING] Rule 2: org.apache.maven.plugins.enforcer.RequireOS failed with messag
e:
OS Arch: amd64 Family: windows Name: windows 7 Version: 6.1 is not allowed by Fa
mily=unix
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.000s
[INFO] Finished at: Tue Jan 08 11:03:01 IST 2013
[INFO] Final Memory: 7M/132M

同样,如果我用 3.0.6 更新了 Maven 最低要求,而我的 Maven 版本是 3.0.4,则它会这样抱怨:

[INFO] --- maven-enforcer-plugin:1.2:enforce (enforce-versions) @ EnforceJavaVer
sionDemo ---
[WARNING] Rule 0: org.apache.maven.plugins.enforcer.RequireMavenVersion failed w
ith message:
Detected Maven Version: 3.0.4 is not in the allowed range 3.0.6.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.000s
[INFO] Finished at: Tue Jan 08 11:03:01 IST 2013
[INFO] Final Memory: 7M/132M

您也可以测试最低的 Java 版本。

学习愉快!

参考: http://maven.apache.org/enforcer/maven-enforcer-plugin/usage.html

从数据库中插入/选择 Blob 的 Hiberate 示例

原文: https://howtodoinjava.com/hibernate/hibernate-example-of-insertselect-blob-from-database/

在先前的 Hiberate 教程中,我们了解了一级缓存二级缓存和一些映射示例等。这是 Hiberate 相关教程的完整列表。 在本文中,我将举一个使用 hibernate 将 BLOB 数据插入数据库并使用 hibernate 实体从数据库中获取数据的示例。

简而言之,插入和获取 BLOB 数据(例如图像)需要两个步骤:将数据库列类型定义为“BLOB”,并且在实体中具有“字节数组”类型的字段。

让我们举个例子,其中,我将 Windows C 驱动器中的“test.png”图像插入数据库(MySQL)。 然后,我将再次从数据库中读取图像数据并将其存储到其他位置。

Hiberate 实体

请注意,我已将数据字段声明为byte[]

@Entity
@Table(name = "TBL_IMAGES")
public class ImageWrapper implements Serializable {

	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	private Integer id;

	@Column(name = "IMAGE_NAME", unique = false, nullable = false, length = 100)
	private String imageName;

	@Column(name = "DATA", unique = false, nullable = false, length = 100000)
	private byte[] data;

	//Getters and Setters
}

将 Blob 数据插入数据库

让我们看一下代码:

	Session session = HibernateUtil.getSessionFactory().openSession();
	session.beginTransaction();

	File file = new File("C:\test.png");
	byte[] imageData = new byte[(int) file.length()];

	try {
		FileInputStream fileInputStream = new FileInputStream(file);
		fileInputStream.read(imageData);
		fileInputStream.close();
	} catch (Exception e) {
		e.printStackTrace();
	}

	ImageWrapper image = new ImageWrapper();
	image.setImageName("test.jpeg");
	image.setData(imageData);

	session.save(image);	//Save the data

	session.getTransaction().commit();
	HibernateUtil.shutdown();

执行完上述代码后,您可以验证是否已在数据库中创建表。 并且创建了一个 BLOB 列来保存图像数据。

Hibernate blob example

Hibernate blob 示例

从数据库读取 Blob 数据

这很简单,实际上您不需要执行任何其他操作。 上面的实体定义可以正常工作。

	Session session = HibernateUtil.getSessionFactory().openSession();
	session.beginTransaction();

	ImageWrapper imgNew = (ImageWrapper)session.get(ImageWrapper.class, 1);
	byte[] bAvatar = imgNew.getData();

	try{
		FileOutputStream fos = new FileOutputStream("C:\temp\test.png"); 
		fos.write(bAvatar);
		fos.close();
	}catch(Exception e){
		e.printStackTrace();
	}

	session.getTransaction().commit();
	HibernateUtil.shutdown();

Hiberate 配置

作为参考,这是我在此示例中使用的配置:

hibernate.cfg.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test</property>
        <property name="hibernate.connection.password">password</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">true</property>
		<property name="hibernate.hbm2ddl.auto">create</property>
        <mapping class="hibernate.test.dto.ImageWrapper"></mapping>
    </session-factory>
</hibernate-configuration>

以下也是HibernateUtil.java的代码

HibernateUtil.java

public class HibernateUtil {
	private static final SessionFactory sessionFactory = buildSessionFactory();

    @SuppressWarnings("deprecation")
	private static SessionFactory buildSessionFactory() {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            return new AnnotationConfiguration().configure(new File
            		("D:\Latest Setup\eclipse_juno_workspace\hibernate-test-project\hibernate.cgf.xml"))
            		.buildSessionFactory();

        }
        catch (Throwable ex) {
            // Make sure you log the exception, as it might be swallowed
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public static void shutdown() {
    	getSessionFactory().close();
    }
}

如果仍然感到麻烦,请下载随附的源代码。

源码下载

祝您学习愉快!

Hiberate save()saveOrUpdate()方法

原文: https://howtodoinjava.com/hibernate/hibernate-save-and-saveorupdate/

Hiberate 仅与持久实体一起使用,并且持久实体是附加到任何 Hiberate 会话的类。 请注意,使用 Hiberate 注解映射的类的实例的创建不会自动将对象持久保存到数据库中。 将其附加到有效的 Hiberate 会话后,必须显式保存它。

在本教程中,学习在不同用例下使用 hibernate save()saveOrUpdate()方法。

1. Hibernate save()方法

在 Hiberate 下,我们通常使用save()方法的以下两个版本之一:

public Serializable save(Object object) throws HibernateException
public Serializable save(String entityName,Object object) throws HibernateException

两种save()方法都将瞬态对象引用(不能为null)作为参数。 第二种方法采用了额外的参数entityName,如果您已将多个实体映射到 Java 类,则该参数很有用。 在这里,您可以使用save()方法指定要保存的实体。

一个演示 hibernate save()方法的简单示例。

@Entity 
@Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
   private static final long serialVersionUID = -1798070786993154676L;
   @Id
   @Column(name = "ID", unique = true, nullable = false)
   private Integer	employeeId;

   @Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
   private String	firstName;

   @Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
   private String	lastName;

   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (!(o instanceof EmployeeEntity)) return false;

       EmployeeEntity otherEmployee = (EmployeeEntity) o;

       if (getEmployeeId() != null ?
           !getEmployeeId().equals(otherEmployee.getEmployeeId()) : otherEmployee.getEmployeeId() != null)
           return false;
       if (getFirstName() != null ?
           !getFirstName().equals(otherEmployee.getFirstName()) : otherEmployee.getFirstName() != null)
           return false;
       if (getLastName() != null ?
           !getLastName().equals(otherEmployee.getLastName()) : otherEmployee.getLastName() != null)
           return false;

       return true;
   }

   @Override
   public int hashCode() {
   int result = getEmployeeId() != null ? getEmployeeId().hashCode() : 0;
       result = 31 * result + (getFirstName() != null ? getFirstName().hashCode() : 0);
       result = 31 * result + (getLastName() != null?getLastName().hashCode() : 0);
       return result;
   }

   //Getters and Setters are hidden here
}

现在,保存此 Hiberate 实体。

public class SimplestSaveEntityExample
{
   public static void main(String[] args)
   {
      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      //Create new Employee object
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");

      //Save employee
      sessionOne.save(emp);

      sessionOne.getTransaction().commit();
      HibernateUtil.shutdown();
   }    
}

程序输出。

Hibernate: insert into Employee (FIRST_NAME, LAST_NAME, ID) values (?, ?, ?)

1.1 在持久实体上调用save()方法

我们保存了Employee实体。 太简单。 但是实际上,它不是那么简单的用例。 在那里,您可能需要再次更新员工实体,然后在另一个会话中再次保存。 您是否应该再次调用save()方法? 我们来看看。

public class SaveEntityAgainInAnotherSession
{
   public static void main(String[] args)
   {
      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      //Create new Employee object
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");

      //Save employee
      sessionOne.save(emp);
      sessionOne.getTransaction().commit();

      Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
      sessionTwo.beginTransaction();

      emp.setLastName("temp");
      //Save employee again second time
      sessionTwo.save(emp);

      sessionTwo.getTransaction().commit();
      HibernateUtil.shutdown();
   }    
}

程序输出:

Hibernate: insert into Employee (FIRST_NAME, LAST_NAME, ID) values (?, ?, ?)
Hibernate: insert into Employee (FIRST_NAME, LAST_NAME, ID) values (?, ?, ?)
 WARN SqlExceptionHelper:144 - SQL Error: -104, SQLState: 23000
ERROR SqlExceptionHelper:146 - Violation of unique constraint SYS_PK_49: duplicate value(s) for column(s) ID in statement [insert into Employee (FIRST_NAME, LAST_NAME, ID) values (?, ?, ?)]
 INFO AbstractBatchImpl:208 - HHH000010: On release of batch it still contained JDBC statements

Hiberate 在这里试图再次插入实体。 尽管由于主键检查而导致失败,但其他实体的检查可能不存在,并且最终可能会出现重复的行。

注意:虽然第二种save()方法导致在不同会话中重复行,但是在同一会话中它们将正常工作。

看下面的例子。

public class SaveEntityAgainInSameSession
{
   public static void main(String[] args)
   {
      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      //Create new Employee object
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");

      //Save employee
      sessionOne.save(emp);

      emp.setLastName("temp");
      //Save employee again second time
      sessionOne.save(emp);

      sessionOne.getTransaction().commit();
      HibernateUtil.shutdown();
   }    
}

程序输出:

Hibernate: insert into Employee (FIRST_NAME, LAST_NAME, ID) values (?, ?, ?)
Hibernate: update Employee set FIRST_NAME=?, LAST_NAME=? where ID=?

令人困惑。 对? 让我们简单点。 规则如下:

请记住,您不应在持久实体(与任何 Hibernate 会话相关联的实体)上调用save()方法。 对持久实体所做的任何更改都会自动保存。

1.2 持久实体的状态变化

对持久实体的任何更改都会自动保存。 让我们以简单的示例了解这一概念。

public class NoSaveCallForPersistentEntity
{
   public static void main(String[] args)
   {
      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      //Create new Employee object
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");

      //Save employee
      sessionOne.save(emp);

      emp.setLastName("temp");

      sessionOne.getTransaction().commit();

      //Let's see what got updated in DB
      Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
      sessionTwo.beginTransaction();

      EmployeeEntity employee = ( EmployeeEntity ) sessionTwo.load(EmployeeEntity.class, 1);
      System.out.println(employee.getLastName());

      sessionTwo.getTransaction().commit();
      HibernateUtil.shutdown();
   }    
}

程序输出:

Hibernate: insert into Employee (FIRST_NAME, LAST_NAME, ID) values (?, ?, ?)
Hibernate: update Employee set FIRST_NAME=?, LAST_NAME=? where ID=?

Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_, 
employeeen0_.LAST_NAME as LAST_NAM3_1_0_ from Employee employeeen0_ where employeeen0_.ID=?

temp

在上面的示例中,我们使用第一种save()方法使emp对象持久化。 之后,当我们将姓氏更新为“temp”时,将按预期执行更新查询。 我们也在返回的数据中对此进行了验证。 这是使用 Hiberate 持久化实体的正确方法。

2. Hiberate saveOrUpdate()方法

在讨论save()方法时,我们忘记了必须在另一个会话中保存持久化实体而导致重复键错误的情况。 这也是一个有效的方案。

要处理这种情况,必须使用saveOrUpdate()方法。 严格来说,即使对于非持久化实体,也应使用saveOrUpdate()。 就个人而言,我认为这样做没有任何危害。 虽然,这可能会让您有些粗心。 所以要小心。

2.1 Hibernate saveOrUpdate()示例

让我们看看如何将saveOrUpdate()方法与save()方法持久化的实体一起使用。

public class SaveOrUpdateMethodExample
{
   public static void main(String[] args)
   {
      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      //Create new Employee object
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");

      //Save employee
      sessionOne.save(emp);
      sessionOne.getTransaction().commit();

      Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
      sessionTwo.beginTransaction();

      emp.setLastName("temp");
      //Save employee again second time
      sessionTwo.saveOrUpdate(emp);

      sessionTwo.getTransaction().commit();
      HibernateUtil.shutdown();
   }  
}

程序输出:

Hibernate: insert into Employee (FIRST_NAME, LAST_NAME, ID) values (?, ?, ?)
Hibernate: select employeeen_.ID, employeeen_.FIRST_NAME as FIRST_NA2_1_, employeeen_.LAST_NAME as 
		LAST_NAM3_1_ from Employee employeeen_ where employeeen_.ID=?
Hibernate: update Employee set FIRST_NAME=?, LAST_NAME=? where ID=?

现在,我们可以使用saveOrUpdate()方法保存实体并更新实体。

请记住,如果您使用了saveOrUpdate()方法代替了上面的save()方法,那么结果也将是相同的。 saveOrUpdate()既可以用于持久实体也可以用于非持久实体。 持久化实体将得到更新,并且瞬态实体将被插入数据库中。

3. 关于生产代码的建议 - 最佳实践

尝试在生产代码中使用上述代码是不明智的。 理想情况下,您要做的是将 VO 对象传递到 DAO 层,从会话中加载实体,并通过将 VO 数据复制到该实体来更新实体。 这意味着更新是在持久对象上进行的,实际上我们根本不必调用Session.save()Session.saveOrUpdate()

一旦对象处于持久状态,当您更改对象的字段和属性时,Hibernate 将管理数据库本身的更新。 真是太好了。

4. 总结

  1. Save()方法将对象存储到数据库中。 它将坚持给定的瞬时实例,首先分配一个生成的标识符。 它返回创建的实体的 ID。
  2. SaveOrUpdate()根据标识符是否存在来调用save()update()。 例如,如果标识符不存在,则将调用save()或将调用update()
  3. 由于 Hiberate 管理持久对象中所做的所有更改,因此实际上调用save()saveOrUpdate()方法的机会很少。

让我知道 hibernate save()saveOrUpdate()方法相关的问题是否尚不清楚或需要更多说明。

学习愉快!

Hiberate 实体/持久化生命周期状态

原文: https://howtodoinjava.com/hibernate/hibernate-entity-persistence-lifecycle-states/

如您所知, Hibernate 适用于应用使用new运算符创建的普通 Java 对象。 以原始形式(没有注解),hibernate 将无法识别您的 java 类; 但是当使用所需的注解正确注解它们时,hibernate 将能够识别它们,然后使用它们,例如,存储在数据库中,更新它们等。这些对象可以说是用 Hiberate 映射的。

给定一个映射到 Hibernate 的对象的实例,它可以处于四种不同状态中的任何一种:瞬态,持久,分离或删除。 今天,我们将在本教程中学习它们。

瞬态对象

瞬态对象存在于堆内存中。 Hibernate 不会管理瞬态对象,也不会对瞬态对象进行更改。

Transient objects are independent of Hibernate

瞬态对象独立于 Hibernate

要持久保存对瞬态对象的更改,您必须要求会话将瞬态对象保存到数据库,此时,Hibernate 会为该对象分配一个标识符,并将该对象标记为处于持久状态。

持久对象

持久对象存在于数据库中,并且 Hibernate 管理持久对象的持久化。

Persistent objects are maintained by Hibernate

持久对象由 Hibernate 维护

如果字段或属性在持久对象上发生更改,则当应用将更改标记为要提交时,Hibernate 将使数据库表示保持最新状态。

分离的对象

分离的对象在数据库中具有表示形式,但是对对象的更改将不会反映在数据库中,反之亦然。 下图显示了对象与数据库的临时分离。

Detached objects exist in the database but are not maintained by Hibernate

分离的对象存在于数据库中,但不能由 Hibernate 维护

可以通过关闭与之关联的会话来创建分离的对象,或者通过调用该会话的evict()方法将其从会话中逐出。

您可能考虑这样做的原因之一是从数据库中读取对象,修改对象在内存中的属性,然后将结果存储在数据库以外的其他位置。 这将是对对象进行深层复制的替代方法。

为了保留对分离对象所做的更改,应用必须将其重新附加到有效的 Hibernate 会话。 当您的应用通过引用分离对象在新会话上调用loadrefreshmergeupdate()save()方法之一时,分离实例可以与新的 Hibernate 会话关联。 调用之后,分离的对象将是由新的 Hibernate 会话管理的持久对象。

删除的对象

删除的对象是由 Hibernate 管理的对象(换句话说,是持久对象),已传递给会话的remove()方法。 当应用将会话中保存的更改标记为要提交时,将删除数据库中与已删除对象相对应的条目。

现在,我们不要记下本教程的要点。

项目要点

  1. 新创建的 POJO 对象将处于瞬态状态。 瞬态对象不代表数据库的任何行,即不与任何会话对象相关联。 它是简单的简单 Java 对象。
  2. 持久对象代表数据库的一行,并且始终与某些唯一的 Hiberate 会话相关联。 Hiberate 跟踪对持久对象的更改,并在提交调用时将其保存到数据库中。
  3. 分离的对象是过去曾经持久化的对象,现在它们不再持久化。 要保留对分离对象所做的更改,必须将它们重新连接以 Hiberate 会话。
  4. 删除的对象是已传递给会话的remove()方法的持久对象,并且一旦将会话中保存的更改提交给数据库,它们就会很快被删除。

祝您学习愉快!

Hibernate 4:如何构建SessionFactory

原文: https://howtodoinjava.com/hibernate/hibarnate-4-how-to-build-sessionfactory/

如果您一直在观看以前的 Hiberate 版本,那么您一定已经注意到它们已经连续快速弃用了许多类。 不推荐使用的类是AnnotationConfigurationServiceRegistryBuilder。 这些类用于通过 Java 代码以及是否在应用中使用它们来构建会话工厂。 它们可能效果很好,但是您总是会看到一些关于其贬值的恼人警告。

在本教程中,我将给出一个示例,该示例在不使用上述不推荐使用的类的情况下构建 Hiberate SessionFactory。 我正在使用最新的 Hiberate 版本,即 Hibernate 4.3.6.Final,因此您可以确保使用最新的方法来构建会话工厂。

建立SessionFactory时使用的类

我已经使用以下类在 Hiberate 4 中构建SessionFactory

  1. Configuration:代替不建议使用的 AnnotationConfiguration
  2. StandardServiceRegistryBuilder :代替不推荐使用的ServiceRegistryBuilder

如何在 Hibernate 4 中构建SessionFactory

使用下面的示例代码在 hibernate 4 中构建会话工厂。可以根据需要随意调整代码。

package com.howtodoinjava.demo.util;

import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;

public class HibernateUtil
{
   private static SessionFactory sessionFactory = buildSessionFactory();

   private static SessionFactory buildSessionFactory()
   {
      try
      {
         if (sessionFactory == null)
         {
            Configuration configuration = new Configuration().configure(HibernateUtil.class.getResource("/hibernate.cfg.xml"));
            StandardServiceRegistryBuilder serviceRegistryBuilder = new StandardServiceRegistryBuilder();
            serviceRegistryBuilder.applySettings(configuration.getProperties());
            ServiceRegistry serviceRegistry = serviceRegistryBuilder.build();
            sessionFactory = configuration.buildSessionFactory(serviceRegistry);
         }
         return sessionFactory;
      } catch (Throwable ex)
      {
         System.err.println("Initial SessionFactory creation failed." + ex);
         throw new ExceptionInInitializerError(ex);
      }
   }

   public static SessionFactory getSessionFactory()
   {
      return sessionFactory;
   }

   public static void shutdown()
   {
      getSessionFactory().close();
   }
}

希望上述信息对从 Hiberate 3 迁移到 Hiberate 4 有所帮助。

祝您学习愉快!

Hiberate 实体等价和等同

原文: https://howtodoinjava.com/hibernate/hibernate-entities-equality-and-identity/

在我们的应用中,很多时候我们都面临着这样的情况,我们必须比较两个对象以检查它们的相等性以满足某些业务规则。 在核心 Java 中,我们已经掌握了检查对象是否相等的知识,但是在 Hiberate 下,我们还需要注意一些额外的事情。 让我们了解一下那些额外的概念。

我们已经了解了 Hiberate 实体在其生命周期中的各种状态。 在那里,我们讨论了 Hiberate 大多数仅适用于持久对象。 众所周知,当我们在 hibernate 中有一个持久对象时,该对象代表了两个:

  • 特定 Java 虚拟机(JVM)中类的实例
  • 数据库表中的一行(或多行)

我们对第一个概念了解足够。 我将集中讨论第二点。

从同一会话中获取的对象

从同一 Hibernate 会话再次请求持久对象会返回类的相同 Java 实例,这意味着您可以使用标准 Java ==等式语法比较这些对象。

让我们看一个简单的例子:

public static void main(String[] args)
{
	Session sessionOne = HibernateUtil.getSessionFactory().openSession();
	sessionOne.beginTransaction();

	// Create new Employee object
	EmployeeEntity emp = new EmployeeEntity();
	emp.setFirstName("Lokesh");
	emp.setLastName("Gupta");
	//Save employee
	sessionOne.save(emp);

	sessionOne.getTransaction().commit();

	//Get employee id
	Integer genEmpId = emp.getEmployeeId();

	//New session where we will fetch the employee two times and compare the objects
	Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
	sessionTwo.beginTransaction();

	EmployeeEntity employeeObj1 = (EmployeeEntity) sessionTwo.get(EmployeeEntity.class, genEmpId);
	EmployeeEntity employeeObj2 = (EmployeeEntity) sessionTwo.get(EmployeeEntity.class, genEmpId);

	//Checking equality 
	System.out.println(employeeObj1 == employeeObj2);

	HibernateUtil.shutdown();
}

Output:

true

您可以在上面看到,我们在EmployeeEntity上有两个实例,并且它们实际上都是同一个 Java 对象实例。

从不同会话中获取的对象

如果您从多个会话中向请求一个持久对象,则 Hibernate 将为每个会话提供不同的实例,如果您比较这些对象实例,则==运算符将返回false

让我们比较上面示例中的实例“emp”和“employeeObj1”,结果将为假; 因为两者都是在单独的会话中获取的。

System.out.println(emp == employeeObj1);
System.out.println(emp.equals(employeeObj1));

Output:

false
false

因此,如果要在两个不同的会话中比较对象,则需要在 Java 持久化对象上实现equals()方法,无论如何,您都应该经常执行此方法。 (请不要忘记将hashCode()与它一起覆盖。)

阅读更多信息覆盖hashCode()equals()方法

Hibernate 将实际对象包装在代理中,因此请始终在内部使用设置器方法而不是实际属性进行比较。

现在,根据建议添加equals()方法,然后在检查EmployeeEntity上两个实例的相等性时查看行为更改。

@Entity
@Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
   private static final long serialVersionUID = -1798070786993154676L;
   @Id
   @Column(name = "ID", unique = true, nullable = false)
   @GeneratedValue(strategy = GenerationType.SEQUENCE)
   private Integer           employeeId;
   @Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
   private String            firstName;
   @Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
   private String            lastName;

   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (!(o instanceof EmployeeEntity)) return false;

       EmployeeEntity otherEmployee = (EmployeeEntity) o;

       if (getEmployeeId() != null ?
           !getEmployeeId().equals(otherEmployee.getEmployeeId()) : otherEmployee.getEmployeeId() != null)
           return false;
       if (getFirstName() != null ?
           !getFirstName().equals(otherEmployee.getFirstName()) : otherEmployee.getFirstName() != null)
           return false;
       if (getLastName() != null ?
           !getLastName().equals(otherEmployee.getLastName()) : otherEmployee.getLastName() != null)
           return false;

       return true;
   }

   @Override
   public int hashCode() {
   int result = getEmployeeId() != null ? getEmployeeId().hashCode() : 0;
       result = 31 * result + (getFirstName() != null ? getFirstName().hashCode() : 0);
       result = 31 * result + (getLastName() != null?getLastName().hashCode() : 0);
       return result;
   }

   //Setters and Getters
}

现在,使用equals()方法再次检查是否相等。 (==将返回false,我们知道)。

System.out.println(emp.equals(employeeObj1));

Output:

true

现在,两个对象在逻辑上和程序上都是相等的。

项目要点

  1. 从相同的 Hibernate 会话再次请求持久对象将返回类的“相同 Java 实例”。
  2. 从不同的 Hibernate 会话中请求一个持久对象,将返回一个类的“不同的 Java 实例”。
  3. 最佳实践是,始终在 Hiberate 实体中实现equals()hashCode()方法; 并始终仅使用equals()方法进行比较。

祝您学习愉快!

Hibernate JPA 级联类型

原文: https://howtodoinjava.com/hibernate/hibernate-jpa-cascade-types/

我们已经在之前的教程中了解了 Hiberate 中的映射关联实体,例如一对一映射一对多映射。 每当要保存关系所有者实体时,我们都想保存映射的实体。 为此,我们使用了“CascadeType”属性。 在此 JPA 级联类型教程中,我们将通过CascadeType了解各种可用的级联选项。

JPA 级联类型如何工作?

在继续之前,让我们看一下如何在代码中定义此级联类型属性。 让我们举个例子来更清楚地了解。 假设一个雇员可以有多个帐户; 但一个帐户只能与一名员工关联。 为了清楚起见,让我们以最少的信息创建实体。

EmployeeEntity.java

@Entity 
@Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
	private static final long serialVersionUID = -1798070786993154676L;
	@Id
	@Column(name = "ID", unique = true, nullable = false)
	private Integer           employeeId;
	@Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
	private String            firstName;
	@Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
	private String            lastName;

	@OneToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
	@JoinColumn(name="EMPLOYEE_ID")
	private Set<AccountEntity> accounts;

	//Getters and Setters Ommited
}

AccountEntity.java

@Entity
@Table(name = "Account")
public class AccountEntity implements Serializable
{
	private static final long serialVersionUID = 1L;
	@Id
	@Column(name = "ID", unique = true, nullable = false)
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	private Integer           accountId;
	@Column(name = "ACC_NO", unique = false, nullable = false, length = 100)
	private String            accountNumber;

	@OneToOne (mappedBy="accounts",  fetch = FetchType.LAZY)
	private EmployeeEntity employee;

}

查看上面EmployeeEntity.java源代码中的粗体行。 它定义了“cascade=CascadeType.ALL”,从本质上讲意味着EmployeeEntity上发生的任何更改也必须级联到AccountEntity。 如果您保存员工,则所有关联的帐户也将保存到数据库中。 如果删除雇员,则与该雇员关联的所有帐户也将被删除。 很简单。

但是,如果我们只希望级联仅保存操作而不删除级联,该怎么办。 然后,我们需要使用以下代码明确指定它。

@OneToMany(cascade=CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinColumn(name="EMPLOYEE_ID")
private Set<AccountEntity> accounts;

现在,仅当使用员工实例调用save()persist()方法时,才会保留帐户。 如果在会话中调用任何其他方法,则该方法的效果不会影响/级联到帐户。

JPA 级联类型

Java 持久化架构支持的级联类型如下:

  1. CascadeType.PERSIST :级联类型presist表示save()persist()操作级联到相关实体。
  2. CascadeType.MERGE :级联类型merge表示在合并所有者实体时会合并相关实体。
  3. CascadeType.REFRESH :级联类型refreshrefresh()操作执行相同的操作。
  4. CascadeType.REMOVE :级联类型remove在删除所有者实体时会删除与此设置关联的所有相关实体。
  5. CascadeType.DETACH :如果发生“手动分离”,则级联类型detach分离所有相关实体。
  6. CascadeType.ALL :级联类型all是上述所有级联操作的简写。

JPA 中没有默认级联类型。 默认情况下,没有操作级联。

级联配置选项接受一个CascadeTypes数组。 因此,如我们的示例所示,为了在一对多关系的级联操作中仅包括刷新和合并,您可能会看到以下内容:

@OneToMany(cascade={CascadeType.REFRESH, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinColumn(name="EMPLOYEE_ID")
private Set<AccountEntity> accounts;

上述级联将导致仅合并和刷新帐户集合。

Hiberate 级联类型

现在,让我们了解在哪种情况下使用 Hiberate 的 Hiberate 方式。

除了 JPA 提供的级联类型之外,Hiberate 中还有一个级联操作,它不是上面讨论的正常设置的一部分,称为“孤例删除”。 将拥有的对象从其拥有的关系中删除后,该对象将从数据库中删除。

让我们看一个例子。 在我们的“雇员和帐户”实体示例中,我对它们进行了如下更新,并在帐户上提到“orphanRemoval=true”。 从本质上讲,这意味着每当我要删除“帐户组中的帐户”时(即我要删除该帐户与Employee之间的关系); 与数据库上任何其他雇员(即孤例)没有关联的帐户实体也应删除。

EmployeeEntity.java

@Entity 
@Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
	private static final long serialVersionUID = -1798070786993154676L;
	@Id
	@Column(name = "ID", unique = true, nullable = false)
	private Integer           employeeId;
	@Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
	private String            firstName;
	@Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
	private String            lastName;

	@OneToMany(orphanRemoval = true, mappedBy = "employee")
	private Set<AccountEntity> accounts;

}

AccountEntity.java

@Entity (name = "Account")
@Table(name = "Account")
public class AccountEntity implements Serializable
{
	private static final long serialVersionUID = 1L;
	@Id
	@Column(name = "ID", unique = true, nullable = false)
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	private Integer           accountId;
	@Column(name = "ACC_NO", unique = false, nullable = false, length = 100)
	private String            accountNumber;

	@ManyToOne
	private EmployeeEntity employee;
}

TestOrphanRemovalCascade.java

public class TestOrphanRemovalCascade
{
   public static void main(String[] args)
   {
      setupTestData();

      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      org.hibernate.Transaction tx = sessionOne.beginTransaction();

      //Load the employee in another session
      EmployeeEntity employee = (EmployeeEntity) sessionOne.load(EmployeeEntity.class, 1);
      //Verify there are 3 accounts
      System.out.println("Step 1 : " + employee.getAccounts().size());

      //Remove an account from first position of collection
      employee.getAccounts().remove(employee.getAccounts().iterator().next());

      //Verify there are 2 accounts in collection
      System.out.println("Step 2 : " + employee.getAccounts().size());

      tx.commit();
      sessionOne.close();

      //In another session check the actual data in database
      Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
      sessionTwo.beginTransaction();

      EmployeeEntity employee1 = (EmployeeEntity) sessionTwo.load(EmployeeEntity.class, 1);
      //Verify there are 2 accounts now associated with Employee
      System.out.println("Step 3 : " + employee1.getAccounts().size());

      //Verify there are 2 accounts in Account table
      Query query = sessionTwo.createQuery("from Account a");
      @SuppressWarnings("unchecked")
      List<AccountEntity> accounts = query.list();
      System.out.println("Step 4 : " + accounts.size());

      sessionTwo.close();

      HibernateUtil.shutdown();
   }  

   private static void setupTestData(){
      Session session = HibernateUtil.getSessionFactory().openSession();
      session.beginTransaction();

      //Create Employee
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");
      session.save(emp);

      //Create Account 1
      AccountEntity acc1 = new AccountEntity();
      acc1.setAccountId(1);
      acc1.setAccountNumber("11111111");
      acc1.setEmployee(emp);
      session.save(acc1);

      //Create Account 2
      AccountEntity acc2 = new AccountEntity();
      acc2.setAccountId(2);
      acc2.setAccountNumber("2222222");
      acc2.setEmployee(emp);
      session.save(acc2);

      //Create Account 3
      AccountEntity acc3 = new AccountEntity();
      acc3.setAccountId(3);
      acc3.setAccountNumber("33333333");
      acc3.setEmployee(emp);
      session.save(acc3);

      session.getTransaction().commit();
      session.close();
   }
}

Output:

Hibernate: insert into Employee (FIRST_NAME, LAST_NAME, ID) values (?, ?, ?)
Hibernate: insert into Account (ACC_NO, employee_ID, ID) values (?, ?, ?)
Hibernate: insert into Account (ACC_NO, employee_ID, ID) values (?, ?, ?)
Hibernate: insert into Account (ACC_NO, employee_ID, ID) values (?, ?, ?)
Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_, employeeen0_.LAST_NAME as 
LAST_NAM3_1_0_ from Employee employeeen0_ where employeeen0_.ID=?
Hibernate: select accounts0_.employee_ID as employee3_1_0_, accounts0_.ID as ID1_0_0_, accounts0_.ID as ID1_0_1_, 
accounts0_.ACC_NO as ACC_NO2_0_1_, accounts0_.employee_ID as employee3_0_1_ from Account accounts0_ where accounts0_.employee_ID=?
Step 1 : 3
Step 2 : 2
Hibernate: delete from Account where ID=?
Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_, employeeen0_.LAST_NAME as 
LAST_NAM3_1_0_ from Employee employeeen0_ where employeeen0_.ID=?
Hibernate: select accounts0_.employee_ID as employee3_1_0_, accounts0_.ID as ID1_0_0_, accounts0_.ID as ID1_0_1_, 
accounts0_.ACC_NO as ACC_NO2_0_1_, accounts0_.employee_ID as employee3_0_1_ from Account accounts0_ where accounts0_.employee_ID=?
Step 3 : 2
Hibernate: select accountent0_.ID as ID1_0_, accountent0_.ACC_NO as ACC_NO2_0_, accountent0_.employee_ID as employee3_0_ 
from Account accountent0_
Step 4 : 2

这是从集合中删除匹配项/不匹配项的好方法(即多对一或一对多关系)。 您只需从集合中删除该项目,然后 Hiberate 即可为您处理其余所有事情。 它将检查是否从任何地方引用了实体; 如果不是,它将从数据库本身中删除该实体。

让我知道您对Hiberate 5 级联类型JPA 级联类型的想法和问题。

学习愉快!

阅读更多:

有关级联类型的 Oracle 博客

Hibernate 延迟加载教程

原文: https://howtodoinjava.com/hibernate/lazy-loading-in-hibernate/

在任何应用中,Hiberate 都以急切或延迟模式从数据库读取数据。 Hiberate 延迟加载是指按需延迟加载数据时的策略。

1. Hiberate 延迟加载 - 我们为什么需要它?

考虑一种常见的互联网 Web 应用:在线商店。 商店维护产品目录。 在最原始的级别上,可以将其建模为管理一系列产品实体的目录实体。 在一家大型商店中,可能有成千上万种产品归为各种重叠类别。

当客户端访问商店时,必须从数据库中加载目录。 我们可能不希望实现将代表成千上万种产品的每个实体中的每一个加载到内存中。 对于足够大的零售商,鉴于机器上可用的物理内存量,这甚至可能是不可能的。

即使这是可能的,也可能会削弱网站的性能。 相反,我们只希望加载目录,可能还需要加载类别。 仅当用户向下钻取类别时,才应从数据库中加载该类别中的一部分产品。

为了解决此问题,Hibernate 提供了一种名为延迟加载的工具。 启用后,仅当直接请求实体的关联实体时,才会加载它们。

2. 延迟加载如何解决上述问题

现在,当我们了解了问题之后,让我们了解延迟加载实际上如何在现实生活中提供帮助。 如果我们考虑解决上面讨论的问题,那么我们将以以下方式访问类别(或目录):

//Following code loads only a single category from the database:
Category category = (Category)session.get(Category.class,new Integer(42));

但是,如果访问了该类别的所有产品,并且执行了延迟加载,则会根据需要从数据库中拉出产品。 例如,在以下代码段中,将加载关联的产品对象,因为在第二行中已明确引用了该产品。

//Following code loads only a single category from the database
Category category = (Category)session.get(Category.class,new Integer(42));

//This code will fetch all products for category 42 from database - "NOW"
Set<Product> products = category.getProducts();

这解决了我们仅在需要时才加载产品的问题。

3. 如何在 Hiberate 下启用延迟加载

在进一步进行操作之前,重要的是要重述延迟加载的默认行为,以防使用 Hiberate 映射和注解。

默认行为是紧急加载“属性值”和延迟加载“集合”。 如果您以前使用过普通的 Hibernate 2(映射文件),则默认情况下会急切加载所有引用(包括集合),这与您可能会记得的相反。

另请注意,@OneToMany@ManyToMany关联默认为LAZY加载; @OneToOne@ManyToOne默认为 EAGER 加载。 记住这一点很重要,以避免将来出现任何陷阱。

要显式启用延迟加载,必须在使用 Hiberate 注解时要延迟加载的关联上使用“fetch = FetchType.LAZY”。

一个 hibernare 延迟加载示例如下所示:

@OneToMany( mappedBy = "category", fetch = FetchType.LAZY )
private Set<ProductEntity> products; 

"FetchType.LAZY"平行的另一个属性是"FetchType.EAGER",它与LAZY正好相反,即,当首次获取所有者实体时,它也会加载关联实体。

4. 延迟加载在 Hiberate 下如何工作

Hibernate 可以在实体和关联上应用延迟加载行为的最简单方法是提供它们的代理实现。 Hibernate 通过替换从实体类派生的代理来拦截对实体的调用。 如果缺少所需信息,将从控制权移交给上级实体的实现之前将其从数据库中加载。

请注意,当关联表示为集合类时,将创建包装器(本质上是集合的代理,而不是集合所包含的实体),并替换为原始集合。 当您访问该集合代理时,您在返回的代理集合中得到的不是代理实体;它是代理实体。 而是它们是实际的实体。 您无需在理解此概念上施加太大压力,因为在运行时几乎没有关系。

5. 延迟加载对分离实体的影响

如我们所知,Hiberate 只能通过会话访问数据库,因此,如果实体与会话分离,并且当我们尝试访问尚未加载的关联(通过代理或集合包装器)时,Hibernate 抛出LazyInitializationException

解决方法是确保通过将实体附加到会话来使该实体再次成为持久化,或者确保在将实体从会话中分离之前,将访问所有需要的字段(将它们加载到实体中)。

这就是这个简单但非常重要的概念,即如何在 Hiberate 中加载延迟对象。 对于 Hiberate 获取策略面试问题的初学者来说,这可能是一个问题。

如果不清楚或您想讨论任何事情,请发表评论。

学习愉快!

Hiberate 条件查询示例

原文: https://howtodoinjava.com/hibernate/hibernate-criteria-queries-tutorial/

Hiberate 提供了三种不同的方法来从数据库检索数据。 我们已经讨论过 HQL 和本机 SQL 查询。 现在,我们将讨论我们的第三个选项,即 Hiberate 条件查询。 使用条件查询 API,您可以使用 Java 构建嵌套的结构化查询表达式,从而提供了 HQL 或 SQL 等查询语言无法进行的编译时语法检查。

条件 API 还包括示例查询(QBE)功能。 这样,您就可以提供示例对象,这些对象包含要检索的属性,而不必逐步说明查询的组件。 它还包括包括count()的投影和聚合方法。 让我们详细探讨它的不同功能。

Table of Contents

1\. Hibernate criteria example
2\. Hibernate criteria - using Restrictions
3\. Hibernate criteria - paging through the result set
4\. Hibernate criteria - Obtain unique result
5\. Hibernate criteria - obtaining distinct results
6\. Hibernate criteria - sort query results
7\. Hibernate criteria - perform associations (joins)
8\. Hibernate criteria - add projections 
9\. Hibernate criteria - query by example (QBE)
10\. Summary

1. Hiberate 条件示例

条件 API 允许您以编程方式构建条件查询对象; org.hibernate.Criteria接口定义这些对象之一的可用方法。 Hiberate Session接口包含几种重载的createCriteria()方法。

将持久化对象的类或其实体名称传递给createCriteria()方法,然后 hibernate 将创建一个Criteria对象,当您的应用执行条件查询时,该对象将返回持久化对象的类的实例。

条件查询的最简单示例是没有可选参数或限制的条件查询 - 条件查询将仅返回与该类相对应的每个对象。

Criteria crit = session.createCriteria(Product.class);
List<Product> results = crit.list();

从这个简单的条件示例继续,我们将向我们的条件查询添加约束,以便我们可以缩减结果集。

2. Hiberate 条件 – 使用限制

使用条件 API,您可以轻松地在查询中使用限制来有选择地检索对象。 例如,您的应用只能检索价格超过 30 美元的产品。 您可以使用add()方法将这些限制添加到Criteria对象。 add()方法采用代表单个限制的org.hibernate.criterion.Criterion对象。 一个条件查询可以有多个限制。

2.1 Restrictions.eq()示例

要检索属性值“等于”你的限制的对象,请使用Restrictions上的eq()方法,如下所示:

Criteria crit = session.createCriteria(Product.class);
crit.add(Restrictions.eq("description","Mouse"));
List<Product> results = crit.list()

上面的查询将搜索所有描述为“Mouse”的产品。

2.2 Restrictions.ne()示例

要检索具有属性值“不等于”你的限制的对象,请使用Restrictions上的ne()方法,如下所示:

Criteria crit = session.createCriteria(Product.class);
crit.add(Restrictions.ne("description","Mouse"));
List<Product> results = crit.list()

上面的查询将搜索所有具有描述的产品,但不包含“Mouse”。

您不能使用不相等的限制来检索该属性在数据库中具有NULL值的记录(在 SQL 中,因此在 Hibernate 中,NULL表示缺少数据,因此无法与数据进行比较)。 如果需要检索具有NULL属性的对象,则必须使用isNull()限制。

2.3 Restrictions.like()Restrictions.ilike()示例

除了搜索完全匹配之外,我们还可以检索具有与给定模式的一部分属性匹配的所有对象。 为此,我们需要使用like()ilike()方法创建一个 SQL LIKE子句。 ilike()方法不区分大小写。

Criteria crit = session.createCriteria(Product.class);
crit.add(Restrictions.like("name","Mou%",MatchMode.ANYWHERE));
List<Product> results = crit.list();

上面的示例使用org.hibernate.criterion.MatchMode对象指定如何将指定的值与存储的数据进行匹配。 MatchMode对象(类型安全的枚举)具有四个不同的匹配项:

  • ANYWHERE:字符串的任意位置
  • END:字符串的末尾
  • EXACT:完全匹配
  • START:字符串的开头

2.4 Restrictions.isNull()Restrictions.isNotNull()示例

isNull()isNotNull()限制使您可以搜索具有(或不具有)空属性值的对象。

Criteria crit = session.createCriteria(Product.class);
crit.add(Restrictions.isNull("name"));
List<Product> results = crit.list();

2.5 Restrictions.gt()Restrictions.ge()Restrictions.lt()Restrictions.le()示例

一些限制对于进行数学比较很有用。 大于比较为gt(),大于或等于比较为ge(),小于比较为lt(),小于或等于比较为le()。 我们可以使用 Java 的类型提升来快速检索价格超过 25 美元的所有此类产品,以转换为Double

Criteria crit = session.createCriteria(Product.class);
crit.add(Restrictions.gt("price", 25.0));
List<Product> results = crit.list();

2.6 结合两个或更多个条件示例

继续,我们可以开始使用条件 API 进行更复杂的查询。 例如,我们可以在逻辑表达式中组合 AND 和 OR 限制。 当我们向条件查询添加多个约束时,它将被解释为 AND,如下所示:

Criteria crit = session.createCriteria(Product.class);
crit.add(Restrictions.lt("price",10.0));
crit.add(Restrictions.ilike("description","mouse", MatchMode.ANYWHERE));
List<Product> results = crit.list();

如果我们希望有两个限制来返回满足两个或两个限制的对象,则需要在Restrictions类上使用or()方法,如下所示:

Criteria crit = session.createCriteria(Product.class);
Criterion priceLessThan = Restrictions.lt("price", 10.0);
Criterion mouse = Restrictions.ilike("description", "mouse", MatchMode.ANYWHERE);
LogicalExpression orExp = Restrictions.or(priceLessThan, mouse);
crit.add(orExp);
List results=crit.list();

我们在此处创建的orExp逻辑表达式将被视为任何其他条件。 因此,我们可以对条件添加另一个限制:

Criteria crit = session.createCriteria(Product.class);
Criterion price = Restrictions.gt("price",new Double(25.0));
Criterion name = Restrictions.like("name","Mou%");
LogicalExpression orExp = Restrictions.or(price,name);
crit.add(orExp);
crit.add(Restrictions.ilike("description","blocks%"));
List results = crit.list();

2.7 将析取对象与条件一起使用

如果我们要创建一个具有两个以上不同条件的 OR 表达式(例如,“price > 25.0 OR name like Mou% OR description not like blocks%”),则可以使用org.hibernate.criterion.Disjunction对象来表示析取。

您可以从Restrictions类的disjunction()工厂方法获取此对象。 分离比在代码中构建 OR 表达式树更方便。 要表示具有两个以上条件的 AND 表达式,可以使用conjunction()方法,尽管可以轻松地将它们添加到Criteria对象中。 与在代码中构建 AND 表达式树相比,该连接更方便。 这是一个使用析取的示例:

Criteria crit = session.createCriteria(Product.class);
Criterion priceLessThan = Restrictions.lt("price", 10.0);
Criterion mouse = Restrictions.ilike("description", "mouse", MatchMode.ANYWHERE);
Criterion browser = Restrictions.ilike("description", "browser", MatchMode.ANYWHERE);
Disjunction disjunction = Restrictions.disjunction();
disjunction.add(priceLessThan);
disjunction.add(mouse);
disjunction.add(browser);
crit.add(disjunction);
List results = crit.list();

2.8 Restrictions.sqlRestriction()示例

sqlRestriction()限制使您可以在条件 API 中直接指定 SQL。 如果您需要使用 Hibernate 通过条件 API 不支持的 SQL 子句,这将非常有用。

您的应用代码无需知道您的类使用的表的名称。 使用{alias}来表示类的表,如下所示:

Criteria crit = session.createCriteria(Product.class);
crit.add(Restrictions.sqlRestriction("{alias}.description like 'Mou%'"));
List<Product> results = crit.list();

3. Hiberate 条件 – 对结果集分页

条件可以解决的一种常见应用模式是通过数据库查询的结果集进行分页。 与查询一样,Criteria接口上有两种分页方法:setFirstResult()setMaxResults()setFirstResult()方法采用一个代表结果集中第一行的整数,从第 0 行开始。您可以使用setMaxResults()方法告诉 Hibernate 检索固定数量的对象。 结合使用这两者,我们可以在 Web 或 Swing 应用中构造一个分页组件。

Criteria crit = session.createCriteria(Product.class);
crit.setFirstResult(1);
crit.setMaxResults(20);
List<Product> results = crit.list();

如您所见,这使得分页结果集变得容易。 您可以增加返回的第一个结果(例如,从 1 到 21,再到 41 等)以翻页结果集。

4. Hiberate 条件 – 获得独特的结果

有时您知道您将从给定查询中仅返回零或一个对象。 这可能是因为您正在计算汇总,或者是因为您的限制自然会导致唯一的结果。 如果要获取单个Object引用而不是List,则Criteria对象上的uniqueResult()方法将返回一个对象或null。 如果结果不止一个,则uniqueResult()方法将抛出HibernateException

以下简短示例演示了一个结果集,该结果集将包含多个结果,但该结果集受setMaxResults()方法限制:

Criteria crit = session.createCriteria(Product.class);
Criterion price = Restrictions.gt("price",new Double(25.0));
crit.setMaxResults(1);
Product product = (Product) crit.uniqueResult();

同样,请注意,如果使用uniqueResult()方法,则需要确保查询仅返回一或零个结果。 否则,Hibernate 将抛出NonUniqueResultException异常。

5. Hiberate 条件 – 获得不同的结果

如果您想使用条件查询的不同结果,则 Hibernate 提供了针对不同实体的结果转换器org.hibernate.transform.DistinctRootEntityResultTransformer,以确保查询的结果集中不会出现重复项。 不同于使用 SQL 的SELECT DISTINCT,不同的结果转换器使用其默认的hashCode()方法比较每个结果,并且仅将具有唯一哈希码的结果添加到结果集中。 这可能是或可能不是您期望从其他等效的 SQL DISTINCT 查询中得到的结果,因此对此要小心。

Criteria crit = session.createCriteria(Product.class);
Criterion price = Restrictions.gt("price",new Double(25.0));
crit.setResultTransformer( DistinctRootEntityResultTransformer.INSTANCE )
List<Product> results = crit.list();

另一个性能说明:比较是在 Hibernate 的 Java 代码中完成的,而不是在数据库中完成的,因此非唯一的结果仍将通过网络传输。

6. Hiberate 条件 – 对查询结果进行排序

使用条件对查询结果进行排序的方式与使用 HQL 或 SQL 进行排序的方式几乎相同。 条件 API 提供了org.hibernate.criterion.Order类,可以根据对象的属性之一将结果集按升序或降序排序。

此示例演示如何使用Order类:

Criteria crit = session.createCriteria(Product.class);
crit.add(Restrictions.gt("price",10.0));
crit.addOrder(Order.desc("price"));
List<Product> results = crit.list();

您可以向Criteria对象添加多个Order对象。 Hibernate 会将它们传递给基础 SQL 查询。 您的结果将按第一顺序排序,然后第一排序中的所有相同匹配项将按第二顺序排序,依此类推。 在幕后, Hibernate 在将属性替换为适当的数据库列名称之后,将其传递给 SQL ORDER BY子句。

7. Hiberate 条件 – 执行关联(连接)

一对多或从多对一连接时,关联有效。 首先,我们将演示如何使用一对多关联来获取价格超过 25 美元的供应商。 请注意,我们为产品属性创建了一个新的Criteria对象,为我们刚刚创建的产品条件添加了限制,然后从供应商Criteria对象获取结果:

Criteria crit = session.createCriteria(Supplier.class);
Criteria prdCrit = crit.createCriteria("products");
prdCrit.add(Restrictions.gt("price",25.0));
List results = crit.list();

换句话说,我们使用多对一关联从供应商 MegaInc 获取所有产品:

Criteria crit = session.createCriteria(Product.class);
Criteria suppCrit = crit.createCriteria("supplier");
suppCrit.add(Restrictions.eq("name","Hardware Are We"));
List results = crit.list();

8. Hiberate 条件 – 添加预测和汇总

您可以将结果集中的结果视为一组行和列,也称为数据投影,而不是使用结果集中的对象。 这类似于您将通过 JDBC 使用SELECT查询中的数据的方式类似。

要使用投影,请从org.hibernate.criterion.Projections工厂类中获取所需的org.hibernate.criterion.Projection对象。 Projections类与Restrictions类相似,因为它提供了几种用于获取Projection实例的静态工厂方法。 获得Projection对象后,使用setProjection()方法将其添加到Criteria对象中。 执行Criteria对象时,该列表包含可以转换为适当类型的对象引用。

8.1 单个聚合(获取行数)

Criteria crit = session.createCriteria(Product.class);
crit.setProjection(Projections.rowCount());
List<Long> results = crit.list();

通过Projections工厂类可用的其他聚合函数包括:

  1. avg(String propertyName):给出属性值的平均值
  2. count(String propertyName):计算属性发生的次数
  3. countDistinct(String propertyName):计算属性包含的唯一值的数量
  4. max(String propertyName):计算属性值的最大值
  5. min(String propertyName):计算属性值的最小值
  6. sum(String propertyName):计算属性值的总和

8.2 多个集合

我们可以将多个投影应用于给定的Criteria对象。 若要添加多个投影,请从Projections类的projectionList()方法中获取一个投影列表。 org.hibernate.criterion.ProjectionList对象具有采用Projection对象的add()方法。 您可以将投影列表传递给Criteria对象上的setProjection()方法,因为ProjectionList实现了Projection接口。

Criteria crit = session.createCriteria(Product.class);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.max("price"));
projList.add(Projections.min("price"));
projList.add(Projections.avg("price"));
projList.add(Projections.countDistinct("description"));
crit.setProjection(projList);
List<object[]> results = crit.list();

8.3 获取选定列

投影的另一个用途是检索单个属性,而不是实体。 例如,我们可以仅从产品表中检索名称和描述,而不是将整个对象表示加载到内存中。

Criteria crit = session.createCriteria(Product.class);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.property("name"));
projList.add(Projections.property("description"));
crit.setProjection(projList);
crit.addOrder(Order.asc("price"));
List<object[]> results = crit.list();

9. Hiberate 条件 – 示例查询(QBE)

在 QBE 中,您可以部分填充该对象的实例,而不必以编程方式使用Criterion对象和逻辑表达式构建Criteria对象。 您可以将此实例用作模板,并让 Hibernate 根据其值为您构建条件。 这可以使您的代码保持整洁,并使您的项目更易于测试。

例如,如果我们有一个用户数据库,则可以构造一个用户对象的实例,设置类型和创建日期的属性值,然后使用条件 API 运行 QBE 查询。 Hibernate 将返回一个结果集,其中包含与设置的属性值匹配的所有用户对象。 在后台,Hibernate 检查Example对象,并构造一个与Example对象的属性相对应的 SQL 片段。

下面的基本示例搜索与示例Supplier对象上的名称匹配的供应商:

Criteria crit = session.createCriteria(Supplier.class);
Supplier supplier = new Supplier();
supplier.setName("MegaInc");
crit.add(Example.create(supplier));
List results = crit.list();

10. 总结

使用条件 API 是开始使用 HQL 开发的绝佳方法。 Hibernate 的开发人员提供了一个干净的 API,用于为使用 Java 对象的查询添加限制。 尽管 HQL 不太难学,但是一些开发人员更喜欢条件查询 API,因为它提供了编译时语法检查,尽管直到运行时才能检查列名和其他与模式相关的信息。

学习愉快!

阅读更多:

Hiberate 文档

Hibernate HQL(Hiberate 查询语言)示例

原文: https://howtodoinjava.com/hibernate/complete-hibernate-query-language-hql-tutorial/

在本 HQL 教程中,了解什么是 Hiberate 查询语言,各种语句的 hql 语法,命名查询和本机 sql 查询,关联和聚合等。

HQL 是一种类似于 SQL 的面向对象的查询语言,但是 HQL 不会对表和列进行操作,而是处理持久化对象及其属性。 这是 hql 与 sql 之间的主要区别。 HQL 是 JPQL(Java 持久化查询语言)的超集。 JPQL 查询是有效的 HQL 查询,但并非所有 HQL 查询都是有效的 JPQL 查询。

HQL 是一种具有自己的语法和语法的语言。 它以字符串形式编写,例如from Product p。 Hibernate 将 HQL 查询转换为常规 SQL 查询。 Hibernate 还提供了一个 API,它使我们也可以直接发出 SQL 查询。

请注意,Hibernator 的查询功能不允许您更改数据库结构。 我们只能更改表中的数据。

Table Of Contents

1\. HQL Syntax
	1.1\. Update Operation
	1.2\. Delete Operation
	1.3\. Insert Operation
	1.4\. Select Operation
2\. The from Clause and Aliases
3\. The select Clause and Projection
4\. Using Named Parameters
5\. Paging Through the Result Set
6\. Obtaining a Unique Result
7\. Sorting Results with the order by Clause
8\. Associations
9\. Aggregate Methods
10\. Named Queries
11\. Using Native SQL
12\. Enable Logging and Commenting

让我们更详细地讨论每一项,从基本内容到更复杂的概念。

1. HQL 语法

HQL 语法定义为 ANTLR 语法。 语法文件包含在 Hibernate 核心下载的语法目录中。 (ANTLR 是用于构建语言解析器的工具)。 让我们在这里概述四个基本 CRUD 操作的语法:

1.1 HQL 更新语句

UPDATE更改数据库中现有对象的详细信息。 不管是否受管,内存中实体都不会更新以反映由于发出UPDATE语句而导致的更改。 这是UPDATE语句的语法:

UPDATE [VERSIONED]
   [FROM] path [[AS] alias] [, ...]
   SET property = value [, ...]
   [WHERE logicalExpression]

  • path – 一个或多个实体的全限定名称
  • alias – 用于缩写对特定实体或其属性的引用,并且在查询中的属性名称不明确时必须使用。
  • VERSIONED – 表示更新将更新时间戳(如果有),该时间戳是要更新的实体的一部分。
  • property – 在FROM路径中列出的实体的属性名称。
  • logicalExpressionwhere子句。

正在进行的更新示例如下所示。 在此示例中,我们使用 hql 多列更新查询来更新员工数据。

Query query=session.createQuery("update Employee set age=:age where name=:name");
query.setInteger("age", 32);
query.setString("name", "Lokesh Gupta");
int modifications=query.executeUpdate();

1.2 HQL 删除语句

DELETE从数据库中删除现有对象的详细信息。 内存中实体将不会更新以反映DELETE语句导致的更改。 这也意味着使用 HQL 进行的删除将不遵循 Hibernate 的级联规则。 但是,如果您在数据库级别指定了级联删除(直接或通过 Hibernate,使用@OnDelete注解),则数据库仍将删除子行。

这是DELETE语句的语法:

DELETE
   [FROM] path [[AS] alias]
   [WHERE logicalExpression]

实际上,删除操作可能如下所示:

Query query=session.createQuery("delete from Account where accountstatus=:status");
query.setString("status", "purged");
int rowsDeleted=query.executeUpdate();

1.3 HQL 插入语句

HQL INSERT不能用于直接插入任意实体 - 它只能用于插入从SELECT查询获得的信息中构造的实体(与普通 SQL 不同,在 SQL 中,可以使用INSERT命令插入任意数据,插入表格,以及插入从其他表格中选择的值)。

这是INSERT语句的语法:

INSERT
   INTO path ( property [, ...])
   select

实体的名称是path。 属性名称是合并的SELECT查询的FROM路径中列出的实体的属性名称。 该选择查询是 HQL SELECT查询(如下一节所述)。

由于此 HQL 语句只能使用 HQL 选择提供的数据,因此其应用可能受到限制。 在实际清除用户之前将用户复制到清除表的示例可能如下所示:

Query query=session.createQuery("insert into purged_accounts(id, code, status) "+
    "select id, code, status from account where status=:status");
query.setString("status", "purged");
int rowsCopied=query.executeUpdate();

1.4 HQL 选择语句

HQL SELECT用于查询数据库中的类及其属性。 这是SELECT语句的语法:

[SELECT [DISTINCT] property [, ...]]
   FROM path [[AS] alias] [, ...] [FETCH ALL PROPERTIES]
   WHERE logicalExpression
   GROUP BY property [, ...]
   HAVING logicalExpression
   ORDER BY property [ASC | DESC] [, ...]

实体的标准名称为pathalias名称可以用于缩写对特定实体或其属性的引用,并且在查询中使用的属性名称不明确时,必须使用alias名称。

property名称是路径中中列出的实体的属性的名称。

如果使用*获取所有属性,则将忽略延迟加载语义,并且将主动加载所有检索到的对象的即时属性(这不适用于递归) 。

WHERE用于使用where子句创建 hql 选择查询

如果列出的属性仅由FROM子句中的别名组成,则可以在 HQL 中省略SELECT子句。 如果我们将 JPA 与 JPQL 一起使用,则 HQL 和 JPQL 之间的区别之一是 JPQL 中需要SELECT子句。

2. HQL – from子句和别名

HQL 中要注意的最重要功能是别名。 Hibernate 允许我们使用as子句为查询中的类分配别名。 使用别名引用回查询中的类。

举个例子:

from Product as p

//or

from Product as product

as关键字是可选的。 我们还可以在类名之后直接指定别名,如下所示:

from Product product

如果我们需要完全限定 HQL 中的类名,只需指定包和类名即可。 Hibernate 将在后台处理大部分此类操作,因此仅当我们的应用中具有重复名称的类时,我们才真正需要这样做。 如果我们必须在 Hibernate 中执行此操作,请使用如下语法:

from com.howtodoinjava.geo.usa.Product

from子句非常基础,对于直接使用对象很有用。 但是,如果要使用对象的属性而不将完整的对象加载到内存中,则必须使用select子句,如下一节所述。

3. HQL select子句和投影

from子句相比,select子句对结果集的控制更多。 如果要获取结果集中对象的属性,请使用select子句。 例如,我们可以对仅返回名称的数据库产品进行投影查询,而不是将整个对象加载到内存中,如下所示:

select product.name from Product product

该查询的结果集将包含一个 Java String对象列表。 此外,我们可以检索数据库中每种产品的价格和名称,如下所示:

select product.name, product.price from Product product

如果您只对一些属性感兴趣,则可以使用这种方法减少到数据库服务器的网络流量,并在应用的计算机上节省内存。

4. HQL 命名参数

Hibernate 在其 HQL 查询中支持命名参数。 这使得编写查询以接受来自用户的输入的查询变得容易,并且您不必防御 SQL 注入攻击。

使用 JDBC 查询参数时,无论何时添加,更改或删除 SQL 语句的各个部分,都需要更新设置其参数的 Java 代码,因为参数是根据它们在语句中出现的顺序进行索引的。 Hibernate 允许您为 HQL 查询中的参数提供名称,因此您不必担心在查询中意外移动参数。

命名参数的最简单示例对参数使用常规 SQL 类型:

String hql = "from Product where price > :price";
Query query = session.createQuery(hql);
query.setDouble("price",25.0);
List results = query.list();

5. HQL – 对结果集分页

通过数据库查询的结果集进行分页是一种非常常见的应用模式。 通常,将分页用于返回大量查询数据的 Web 应用。 Web 应用将浏览数据库查询结果集以为用户构建适当的页面。 如果 Web 应用将每个用户的所有数据都加载到内存中,则该应用将非常慢。 相反,您可以浏览结果集并检索要一次显示一个块的结果。

查询界面上有两种分页方法:setFirstResult()setMaxResults()setFirstResult()方法采用一个代表结果集中第一行的整数,从第 0 行开始。您可以使用setMaxResults()方法告诉 Hibernate 仅检索固定数量的对象。 您的 HQL 保持不变 - 您只需要修改执行查询的 Java 代码即可。

Query query = session.createQuery("from Product");
query.setFirstResult(1);
query.setMaxResults(2);
List results = query.list();
displayProductsList(results);

如果打开 SQL 日志记录,则可以查看 Hibernate 用于分页的 SQL 命令。 对于开源 HSQLDB 数据库,Hibernate 使用toplimit。 Microsoft SQL Server 不支持limit命令,因此 Hibernate 仅使用top命令。 如果您的应用在分页时遇到性能问题,这对于调试非常有用。

如果 HQL 结果集中只有一个结果,则 Hibernate 提供了一种仅获取该对象的快捷方法,如下所述。

6. HQL – 获得独特的结果

HQL 的查询界面提供了一种uniqueResult()方法,用于仅从 HQL 查询中获取一个对象。 尽管查询可能只产生一个对象,但如果将结果限制为仅第一个结果,则也可以将uniqueResult()方法与其他结果集一起使用。 您可以使用上一节中讨论的setMaxResults()方法。

Query对象上的uniqueResult()方法返回单个对象;如果结果为零,则返回null。 如果结果不止一个,则uniqueResult()方法将引发NonUniqueResultException

String hql = "from Product where price>25.0";
Query query = session.createQuery(hql);
query.setMaxResults(1);
Product product = (Product) query.uniqueResult();

7. HQL – 使用order by子句对结果进行排序

要对 HQL 查询的结果进行排序,您需要使用order by子句。 您可以按结果集中对象的任何属性对结果进行排序:升序(asc)或降序(desc)。 如果需要,可以对查询中的多个属性使用排序。 用于排序结果的典型 HQL 查询如下所示:

from Product p where p.price>25.0 order by p.price desc

如果要按多个属性进行排序,则只需将其他属性添加到order by子句的末尾,并以逗号分隔。 例如,您可以按产品价格和供应商名称进行排序,如下所示:

from Product p order by p.supplier.name asc, p.price asc

8. HQL 关联

关联使您可以在 HQL 查询中使用一个以上的类,就像 SQL 允许您在关系数据库中的表之间使用连接一样。 您可以使用join子句向 HQL 查询添加关联。 Hibernate 支持五种不同类型的连接:内部连接,交叉连接,左外部连接,右外部连接和完全外部连接。

如果使用交叉连接,只需在from子句中指定两个类(from Product p, Supplier)。 对于其他连接,请在from子句后使用join子句。 指定连接的类型,要连接的对象属性以及其他类的别名。

您可以使用内部连接获取每个产品的供应商,然后检索供应商名称,产品名称和产品价格,如下所示:

select s.name, p.name, p.price from Product p inner join p.supplier as s

您可以使用类似的语法检索对象:

from Product p inner join p.supplier as s

9,HQL 聚合方法

HQL 支持多种聚合方法,类似于 SQL。 它们在 HQL 中的工作方式与在 SQL 中的工作方式相同,因此您不必学习任何特定的 Hibernate 术语。 区别在于,在 HQL 中,聚合方法适用于持久对象的属性。 您可以使用count(*)语法对结果集中的所有对象进行计数,或使用count(product.name)对具有name属性的结果集中的对象数进行计数。 以下是使用count(*)方法对所有产品进行计数的示例:

select count(*) from Product product

通过 HQL 可用的聚合功能包括:

  1. avg(property name):属性值的平均值
  2. count(property name or *):结果中属性出现的次数
  3. max(property name):属性值的最大值
  4. min(property name):属性值的最小值
  5. sum(property name):属性值的总和

10. HQL 命名查询

命名查询是通过实体上的类级注解创建的; 通常,查询适用于在其源文件中出现的实体,但是并没有绝对的要求。

使用@NamedQueries注解创建命名查询,该注解包含@NamedQuery集的数组; 每个都有一个查询和一个名称。

命名查询的示例可能如下所示:

@NamedQueries({
        @NamedQuery(name = "supplier.findAll", query = "from Supplier s"),
        @NamedQuery(name = "supplier.findByName",
                query = "from Supplier s where s.name=:name"),
})

执行上述命名查询更为简单。

Query query = session.getNamedQuery("supplier.findAll");
List<Supplier> suppliers = query.list();

阅读更多 – Hiberate 命名查询教程

11. HQL – 原生 SQL

尽管您应该尽可能使用 HQL,但是 Hibernate 确实提供了一种直接通过 Hibernate 使用本机 SQL 语句的方法。 使用本机 SQL 的原因之一是您的数据库通过其 SQL 方言支持 HQL 中不支持的某些特殊功能。 另一个原因是您可能想从 Hibernate 应用中调用存储过程。

您可以修改 SQL 语句,使其与 Hibernate 的 ORM 层一起使用。 您确实需要修改 SQL 以包括与对象或对象属性相对应的 Hibernate 别名。 您可以使用{objectname.*}指定对象的所有属性,也可以直接使用{objectname.property}指定别名。

Hibernate 使用这些映射将您的对象属性名称转换为它们的基础 SQL 列。 这可能不是您期望 Hibernate 正常工作的确切方式,因此请注意,您确实需要修改 SQL 语句以获得完整的 ORM 支持。 您尤其会在带有子类的类上遇到本机 SQL 的问题 - 确保您了解如何在单个表或多个表之间映射继承,以便从表中选择正确的属性。

Hibernate 原生 SQL 支持的基础是org.hibernate.SQLQuery接口,该接口扩展了org.hibernate.Query接口。 您的应用将使用Session接口上的createSQLQuery()方法从会话中创建本机 SQL 查询。

public SQLQuery createSQLQuery(String queryString) throws HibernateException

将包含 SQL 查询的字符串传递给createSQLQuery()方法后,应将 SQL 结果与现有的 Hibernate 实体,连接或标量结果相关联。 SQLQuery接口具有addEntity()addJoin()addScalar()方法。

11.1 Hibernate SQL 查询示例

将本机 SQL 与标量结果一起使用是使用本机 SQL 入门的最简单方法。 示例 Java 代码如下所示:

String sql = "select avg(product.price) as avgPrice from Product product";
SQLQuery query = session.createSQLQuery(sql);
query.addScalar("avgPrice",Hibernate.DOUBLE);
List results = query.list();

返回上一对象结果集的本机 SQL 比上一个示例复杂一点。 在这种情况下,我们将需要将实体映射到 SQL 查询。

String sql = "select {supplier.*} from Supplier supplier";
SQLQuery query = session.createSQLQuery(sql);
query.addEntity("supplier", Supplier.class);
List results = query.list();

//Hibernate modifies the SQL and executes the following command against the database:

select Supplier.id as id0_, Supplier.name as name2_0_ from Supplier supplier

12. HQL – 启用日志和注解

Hibernate 可以将 HQL 查询背后的基础 SQL 输出到应用的日志文件中。 如果 HQL 查询未提供您期望的结果,或者查询花费的时间比您想要的长,则此功能特别有用。 这不是您必须经常使用的功能,但是在您必须向数据库管理员寻求帮助来调整 Hibernate 应用时,此功能很有用。

12.1 HQL 日志

查看 Hibernate HQL 查询的 SQL 的最简单方法是使用“show_sql”属性在日志中启用 SQL 输出。 在hibernate.cfg.xml配置文件中将此属性设置为true,Hibernate 会将 SQL 输出到日志中。 当您在应用的输出中查找 Hibernate SQL 语句时,它们的前缀将为“Hibernate:”。

如果您将 log4j 日志记录为 Hibernate 类进行调试,则您将在日志文件中看到 SQL 语句,以及有关 Hibernate 如何解析 HQL 查询并将其转换为 SQL 的大量信息。

12.2 HQL 注解

跟踪 HQL 语句到生成的 SQL 可能很困难,因此 Hibernate 在Query对象上提供了注解功能,使您可以将注解应用于特定查询。 Query接口具有setComment()方法,该方法将String对象作为参数,如下所示:

public Query setComment(String comment)

即使没有使用setComment()方法,Hibernate 也不会在没有其他配置的情况下将注解添加到 SQL 语句中。 您还需要在 Hibernate 配置中将 Hibernate 属性hibernate.use_sql_comments设置为true

如果您设置了此属性,但没有以编程方式在查询中设置注解,则 Hibernate 将在注解中包括用于生成 SQL 调用的 HQL。 我发现这对于调试 HQL 非常有用。

如果启用了 SQL 日志记录,请使用注解来标识应用日志中的 SQL 输出。

目前,这一切都与 HQL 教程有关。 继续访问以了解更多有关 Hiberate 的信息。

学习愉快!

Hibernate @NamedQuery教程

原文: https://howtodoinjava.com/hibernate/hibernate-named-query-tutorial/

Hiberate 中的命名查询是一种技术,用于将 HQL 语句分组在单个位置中,并在以后需要使用它们时以某种名称对其进行引用。 它在很大程度上有助于代码清除,因为这些 HQL 语句不再分散在整个代码中。

除了上面的内容,以下是命名查询的一些次要优势

  1. 快速失败:创建会话工厂时会检查其语法,从而在发生错误时使应用快速失败。
  2. 可重用:可以从多个位置访问和使用它们,从而提高了可重用性。

但是,您必须知道,命名查询确实会使代码的可读性降低,并且有时调试变得更困难,因为您必须找到正在执行的实际查询定义,并且也必须对此有所了解。

性能明智的命名查询并没有多大区别,也不会增加任何开销。

  1. 与实际执行查询的成本相比,将 HQL 查询转换为 SQL 的成本可忽略不计
  2. 缓存查询的内存开销确实很小。 请记住,Hibernate 无论如何都需要在内存中存储所有实体元数据。

在本教程中,我将提供一个使用注解配置的命名查询的示例。

我有一个DepartmentEntity.java类,它映射到数据库中的Department表。 我编写了两个命名查询,一个用于通过其 ID 更新部门名称,第二个用于通过其 ID 选择部门。

命名查询定义具有两个重要属性:

  • name :使用 Hiberate 会话定位名称查询的名称。
  • query:在这里,您将给出 HQL 语句以在数据库中执行。

DepartmentEntity.java

@Entity
@Table(name = "DEPARTMENT", uniqueConstraints = {
					@UniqueConstraint(columnNames = "ID"),
					@UniqueConstraint(columnNames = "NAME") })
@NamedQueries
(
	{
		@NamedQuery(name=DepartmentEntity.GET_DEPARTMENT_BY_ID, query=DepartmentEntity.GET_DEPARTMENT_BY_ID_QUERY),
		@NamedQuery(name=DepartmentEntity.UPDATE_DEPARTMENT_BY_ID, query=DepartmentEntity.UPDATE_DEPARTMENT_BY_ID_QUERY)
	}
)
public class DepartmentEntity implements Serializable {

	static final String GET_DEPARTMENT_BY_ID_QUERY = "from DepartmentEntity d where d.id = :id"; 
	public static final String GET_DEPARTMENT_BY_ID = "GET_DEPARTMENT_BY_ID"; 

	static final String UPDATE_DEPARTMENT_BY_ID_QUERY = "UPDATE DepartmentEntity d SET d.name=:name where d.id = :id"; 
	public static final String UPDATE_DEPARTMENT_BY_ID = "UPDATE_DEPARTMENT_BY_ID"; 

	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	private Integer id;

	@Column(name = "NAME", unique = true, nullable = false, length = 100)
	private String name;

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

为了测试,在上面的命名查询中,我编写了以下代码并执行了两个命名查询。

TestHibernateNamedQuery.java

public class TestHibernateNamedQuery 
{
	public static void main(String[] args) 
	{
		//Open the hibernate session
		Session session = HibernateUtil.getSessionFactory().openSession();
		session.beginTransaction();
		try
		{
			//Update record using named query
			Query query = session.getNamedQuery(DepartmentEntity.UPDATE_DEPARTMENT_BY_ID)
										.setInteger("id", 1)
										.setString("name", "Finance");
			query.executeUpdate();

			//Get named query instance
			query = session.getNamedQuery(DepartmentEntity.GET_DEPARTMENT_BY_ID)
										.setInteger("id", 1);
			//Get all departments (instances of DepartmentEntity)
			DepartmentEntity department = (DepartmentEntity) query.uniqueResult();
			System.out.println(department.getName());
		}
		finally
		{
			session.getTransaction().commit();
			HibernateUtil.shutdown();
		}
	}
}

Output in console:

Hibernate: update DEPARTMENT set NAME=? where ID=?
Hibernate: select department0_.ID as ID0_, department0_.NAME as NAME0_ from DEPARTMENT department0_ where department0_.ID=?
Finance

其余支持代码可以在教程最后随附的源代码中找到。

要点:

  1. 使用 JPA 样式查询语言。 例如,使用实体名代替表名。
  2. 优选将命名查询仅用于基于复杂条件选择记录。 不要过多地使用它们,否则就不会在简单的 JDBC 上使用 ORM。
  3. 请记住,命名查询的结果不会缓存在辅助缓存中,只有查询对象本身会被缓存。
  4. 每当在代码中添加任何命名查询时,都要养成添加几个单元测试用例的习惯。 并立即运行那些单元测试用例。
  5. Hiberate 中不能有两个具有相同名称的命名查询。 Hibernate 在这方面显示出快速失败的行为,并且将显示应用启动本身中的错误。

到目前为止,所有这些都与 Hiberate 中的命名查询有关。

让我知道您的想法和建议。

源码下载

祝您学习愉快!

Maven 创建 Java 项目 – 交互式与非交互式模式

原文: https://howtodoinjava.com/maven/create-java-project-maven/

命令提示符中使用交互式和非交互式模式,学习使用 maven 命令创建 Java 应用项目。

Table of Contents

1\. Maven non-interactive mode
2\. Maven interactive mode

1. 使用 Maven 非交互模式创建 Java 项目

在非交互模式下,maven 使用所有默认选项创建一个空白 Java 项目。 要创建此文件,请输入以下命令。

$ mvn archetype:generate 
		-DgroupId=com.howtodoinjava 
		-DartifactId=DemoJavaProject
		-DarchetypeArtifactId=maven-archetype-quickstart 
		-DinteractiveMode=false

此命令将在工作区中创建一个名为 DemoJavaProject的空白 Java 项目。 您可以选择并使用自己的应用名称。

在下图中,您可以看到当运行上述命令时发生的所有情况。

Maven create java project

您可以在步骤 2 中看到,创建的 Java 项目已经创建了src文件夹,并且还为您创建了默认的pom.xml

2. 使用 Maven 交互模式创建 Java 项目

在交互模式下,您只需要以“mvn archetype:generate”开头,其余选项将使用向导来指定,该向导将一个一个地出现。

$ mvn archetype:generate

让我们看看向导中包含哪些步骤:

2.1 mvn archetype:generate

这是默认模式。 当您键入以上命令时,它将开始创建项目,并在要求archetypeArtifactId的位置停止。 向导将为您显示一个数字列表,供您选择或过滤(因为选项列表很长)。

arche_type_generate

2.2 筛选

可以通过键入原型名称的一部分来完成,例如quickstart

filtering_maven

您需要为我们的简单 Java 项目选择选项 18

2.3 指定原型版本

以 5(或您自己的)的形式执行,groupId以“com.howtodoinjava”的形式执行。

maven_group_id

2.4 定义artifactId

通过所需的 Java 项目名称进行操作。 现在,将有一些其他选项和确认。 回答他们,您的项目就准备好了。

maven_options

您可以看到您的项目是使用默认源文件夹和pom.xml文件创建的。

这样,如果您尝试几次,您将更喜欢这种方法来创建

学习愉快!

Hibernate – 如何定义实体之间的关联映射

原文: https://howtodoinjava.com/hibernate/how-to-define-association-mappings-between-hibernate-entities/

在上一教程中,我们了解了 Hiberate 实体的四种持久状态,并且我们注意到,只有在使用某些注解对 Java 对象进行注解时,hibernate 才能将其标识为持久对象。 否则,将它们视为与数据库没有直接关系的简单普通 Java 对象。 继续,当我们用 JPA 注解对 Java 类进行注解并使它们成为持久化实体时,我们可能会遇到两个实体可以关联并且必须相互引用的情况,无论是单向还是双向。 在实际在 Hiberate 实体之间创建引用之前,让我们了解一些基本知识。

了解实体及其关联

实体可以包含对其他实体的引用,可以直接作为嵌入式属性或字段,也可以通过某种集合(数组,集合,列表等)间接包含对其他实体的引用。 这些关联使用基础表中的外键关系表示。 这些外键将依赖于参与表使用的标识符。

当一对实体中只有一个包含对另一个实体的引用时,关联为单向。 如果关联是相互的,则称为双向

初学者在设计实体模型时的一个常见错误是试图使所有关联都是双向的。 请记住,不是对象模型的自然组成部分的关联不应被强加到其中。 Hibernate 查询语言(HQL)通常被证明是在需要时访问所需信息的更自然的方法。

理想情况下,我们希望指示仅对关系一端的更改会导致外键的任何更新。 实际上,Hibernate 允许我们通过将关联的一端标记为由另一端管理来做到这一点。

在 Hiberate 映射关联中,一个参与类(只有一个)“管理关系”,而另一类则使用“mappedBy”属性“被管理”。 我们不应该使关联的两端都“管理关系”。 永远不要做。

注意,“mappedBy”纯粹是关于实体之间的外键关系如何保存的。 它与使用级联功能保存实体本身无关。

尽管 Hibernate 让我们指定对一个关联的更改将导致对数据库的更改,但是它不允许我们使对关联的一端的更改自动反映在 Java POJO 的另一端。 为此,我们必须使用级联功能。

通过示例了解关联

让我们快速构建示例,以了解我们上面已阅读的有关实体关联的内容以及应如何完成。 在此示例中,我使用两个简单实体(AccountEntityEmployeeEntity),然后在它们之间创建 一对一关联。 然后,我们将看到各种关联选项如何改变运行时行为和复杂性。

AccountEntity.java

@Entity
@Table(name = "Account")
public class AccountEntity implements Serializable
{
   private static final long serialVersionUID = 1L;

   @Id
   @Column(name = "ID", unique = true, nullable = false)
   @GeneratedValue(strategy = GenerationType.SEQUENCE)
   private Integer           accountId;

   @Column(name = "ACC_NO", unique = false, nullable = false, length = 100)
   private String            accountNumber;

   //We will define the association here
   EmployeeEntity            employee;

   //Getters and Setters are not shown for brevity
}

EmployeeEntity.java

@Entity
@Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
   private static final long serialVersionUID = -1798070786993154676L;
   @Id
   @Column(name = "ID", unique = true, nullable = false)
   @GeneratedValue(strategy = GenerationType.SEQUENCE)
   private Integer           employeeId;
   @Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
   private String            firstName;
   @Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
   private String            lastName;

   //We will define the association here
   AccountEntity             account;

   //Getters and Setters are not shown for brevity
}

方案 1)由两个实体管理的关联

在这种类型的关联中,我们将使用@OneToOne注解如下定义关联。

//Inside EmployeeEntity.java
@OneToOne
AccountEntity		account;

//Inside AccountEntity.java
@OneToOne
EmployeeEntity		employee;

通过上述关联,两端都在管理关联,因此必须使用实体 java 文件中定义的设置器方法使用彼此的信息来更新双方。 如果您未这样做,则将无法获取关联的实体信息,并且该信息将作为null返回。

public class TestHibernate
{
   public static void main(String[] args)
   {
      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      // Create new Employee object
      EmployeeEntity emp = new EmployeeEntity();
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");

      // Create new Employee object
      AccountEntity acc = new AccountEntity();
      acc.setAccountNumber("DUMMY_ACCOUNT");
      emp.setAccount(acc);
      //acc.setEmployee(emp);

      sessionOne.save(acc);
      sessionOne.save(emp);
      sessionOne.getTransaction().commit();

      Integer genEmpId = emp.getEmployeeId();
      Integer genAccId  = acc.getAccountId();

      Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
      sessionTwo.beginTransaction();
      EmployeeEntity employee = (EmployeeEntity) sessionTwo.get(EmployeeEntity.class, genEmpId);
      AccountEntity account = (AccountEntity) sessionTwo.get(AccountEntity.class, genAccId);

      System.out.println(employee.getEmployeeId());
      System.out.println(employee.getAccount().getAccountNumber());
      System.out.println(account.getAccountId());
      System.out.println(account.getEmployee().getEmployeeId());

      HibernateUtil.shutdown();
   }
}

Output:

Hibernate: insert into Account (ACC_NO, employee_ID, ID) values (?, ?, ?)
Hibernate: insert into Employee (account_ID, FIRST_NAME, LAST_NAME, ID) values (?, ?, ?, ?)
Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.account_ID as account_4_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_,
			employeeen0_.LAST_NAME as LAST_NAM3_1_0_, accountent1_.ID as ID1_0_1_, accountent1_.ACC_NO as ACC_NO2_0_1_,
			accountent1_.employee_ID as employee3_0_1_, employeeen2_.ID as ID1_1_2_, employeeen2_.account_ID as account_4_1_2_,
			employeeen2_.FIRST_NAME as FIRST_NA2_1_2_, employeeen2_.LAST_NAME as LAST_NAM3_1_2_ from Employee
			employeeen0_ left outer join Account accountent1_ on employeeen0_.account_ID=accountent1_.ID
			left outer join Employee employeeen2_ on accountent1_.employee_ID=employeeen2_.ID where employeeen0_.ID=?

20
DUMMY_ACCOUNT
10
Exception in thread "main" java.lang.NullPointerException
	at com.howtodoinjava.test.TestHibernate.main(TestHibernate.java:43)

您会看到我已经在客户实体中设置了帐户实体,所以我能够得到它。 但是我注解了“acc.setEmployee(emp);”行,因此未在帐户实体内部设置雇员实体,因此我无法获取它。

还要注意上面的插入查询。 这两个表都具有与列名employee_IDaccount_ID对应的外键关联。 它是数据中循环依赖项的示例,可以随时轻松关闭您的应用。

要正确存储上述关系,必须取消注释“acc.setEmployee(emp);”行。 取消注释该行后,您将能够根据需要存储和检索关联,但是仍然存在循环依赖项。

取消注释该行后,输出将如下所示:

Hibernate: insert into Account (ACC_NO, employee_ID, ID) values (?, ?, ?)
Hibernate: insert into Employee (account_ID, FIRST_NAME, LAST_NAME, ID) values (?, ?, ?, ?)
Hibernate: update Account set ACC_NO=?, employee_ID=? where ID=?
Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.account_ID as account_4_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_,
			employeeen0_.LAST_NAME as LAST_NAM3_1_0_, accountent1_.ID as ID1_0_1_, accountent1_.ACC_NO as ACC_NO2_0_1_,
			accountent1_.employee_ID as employee3_0_1_, employeeen2_.ID as ID1_1_2_, employeeen2_.account_ID as account_4_1_2_,
			employeeen2_.FIRST_NAME as FIRST_NA2_1_2_, employeeen2_.LAST_NAME as LAST_NAM3_1_2_ from Employee
			employeeen0_ left outer join Account accountent1_ on employeeen0_.account_ID=accountent1_.ID
			left outer join Employee employeeen2_ on accountent1_.employee_ID=employeeen2_.ID where employeeen0_.ID=?

20
DUMMY_ACCOUNT
10
20

方案 2)由单个实体管理的关联

假设关联是由EmployeeEntity管理的,那么两个实体中的注解将如下所示。

//Inside EmployeeEntity.java
@OneToOne
AccountEntity		account;

//Inside AccountEntity.java
@OneToOne (mappedBy = "account")
EmployeeEntity		employee;

现在告诉 Hiberate ,该关联是由EmployeeEntity管理的,我们将在AccountEntity中添加mappingBy属性以使其易于管理。 现在,要测试上述代码,我们将只需要使用“emp.setAccount(acc);”设置一次关联,并且由员工实体来管理该关系。 AccountEntity不需要明确地知道任何事情。

让我们看看下面的测试运行。

Session sessionOne = HibernateUtil.getSessionFactory().openSession();
sessionOne.beginTransaction();

// Create new Employee object
EmployeeEntity emp = new EmployeeEntity();
emp.setFirstName("Lokesh");
emp.setLastName("Gupta");

// Create new Employee object
AccountEntity acc = new AccountEntity();
acc.setAccountNumber("DUMMY_ACCOUNT");
emp.setAccount(acc);
//acc.setEmployee(emp);

sessionOne.save(acc);
sessionOne.save(emp);
sessionOne.getTransaction().commit();

Integer genEmpId = emp.getEmployeeId();
Integer genAccId  = acc.getAccountId();

Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
sessionTwo.beginTransaction();
EmployeeEntity employee = (EmployeeEntity) sessionTwo.get(EmployeeEntity.class, genEmpId);
AccountEntity account = (AccountEntity) sessionTwo.get(AccountEntity.class, genAccId);

System.out.println(employee.getEmployeeId());
System.out.println(employee.getAccount().getAccountNumber());
System.out.println(account.getAccountId());
System.out.println(account.getEmployee().getEmployeeId());

HibernateUtil.shutdown();

Output:

Hibernate: insert into Account (ACC_NO, ID) values (?, ?)
Hibernate: insert into Employee (account_ID, FIRST_NAME, LAST_NAME, ID) values (?, ?, ?, ?)
Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.account_ID as account_4_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_,
employeeen0_.LAST_NAME as LAST_NAM3_1_0_, accountent1_.ID as ID1_0_1_, accountent1_.ACC_NO as ACC_NO2_0_1_ from Employee employeeen0_
left outer join Account accountent1_ on employeeen0_.account_ID=accountent1_.ID where employeeen0_.ID=?
Hibernate: select employeeen0_.ID as ID1_1_1_, employeeen0_.account_ID as account_4_1_1_, employeeen0_.FIRST_NAME as FIRST_NA2_1_1_,
employeeen0_.LAST_NAME as LAST_NAM3_1_1_, accountent1_.ID as ID1_0_0_, accountent1_.ACC_NO as ACC_NO2_0_0_ from Employee employeeen0_
left outer join Account accountent1_ on employeeen0_.account_ID=accountent1_.ID where employeeen0_.account_ID=?

20
DUMMY_ACCOUNT
10
20

您会看到您不需要告诉帐户实体任何内容(“acc.setEmployee(emp)”已注解)。 员工实体通过两种方式管理关联。

另一个观察结果是关于外键列,我们现在只有一个外键列,即account_IDEmployee表。 因此也没有循环依赖。 都好。

定义各种映射关联的指南

上面的示例显示了如何在一对一映射中管理实体之间的关联。 在上面的示例中,我们也可以选择由AccountEntity管理的关联,并且只需进行较小的代码更改即可完成所有工作。 但是,在其他映射的情况下(例如,一对多或多对一),您将无法随意定义关联。 您需要规则。

下表显示了如何选择关系的一方,该一方应成为双向关联的所有者。 请记住,要使关联成为所有者,您必须将另一端标记为被另一端映射

关联类型 选项/用法
一对一 可以将任一端作为所有者,但应其中之一(只有一个); 如果您未指定此选项,则最终会产生循环依赖项。
一对多 多的一端必须成为联合的所有者。
多对一 这与从相反的角度看的一对多关系相同,因此适用相同的规则:必须使多的一端成为关联的所有者。
多对多 关联的任何一端都可以成为所有者。

如果这一切看起来都令人困惑,请记住关联所有权仅与数据库中外键的管理有关。 我建议您阅读之前的教程,讨论“一对一映射”,“一对多映射”和“多对多映射”的详细信息。 他们将帮助您建立更强大的概念。

如果您有任何问题,请在下面给我留言。

祝您学习愉快!

通过示例了解 Hibernate 一级缓存

原文: https://howtodoinjava.com/hibernate/understanding-hibernate-first-level-cache-with-example/

缓存是 ORM 框架提供的一项功能,可帮助用户获得快速运行的 Web 应用,同时帮助框架本身减少单个事务中对数据库的查询数量。 Hibernate 通过实现一级缓存来实现第二个目标。

Hiberate 中的第一级缓存默认情况下处于启用状态,并且您无需执行任何操作即可使此功能正常工作。 实际上,您甚至不能强行禁用它。

如果我们了解与会话对象关联的事实,那么就很容易理解第一级缓存。 我们知道,会话对象是按需从会话工厂创建的,一旦会话关闭就会丢失。 同样,与会话对象关联的一级缓存仅在会话对象处于活动状态之前可用。 它仅对会话对象可用,并且对于应用任何其他部分中的任何其他会话对象均不可访问。

Hibernate first level cache

重要事实

  1. 一级缓存与“会话”对象关联,应用中的其他会话对象看不到它。
  2. 缓存对象的范围是会话。 一旦会话关闭,缓存的对象将永远消失。
  3. 默认情况下,第一级缓存处于启用状态,您不能禁用它。
  4. 当我们第一次查询实体时,它是从数据库中检索出来的,并存储在与 Hiberate 会话相关的一级缓存中。
  5. 如果我们使用相同的会话对象再次查询相同的对象,则会从缓存中加载该对象,并且不会执行任何 SQL 查询。
  6. 可以使用evict()方法从会话中删除已加载的实体。 如果已使用evict()方法删除了该实体,则该实体的下一次加载将再次进行数据库调用。
  7. 可以使用clear()方法删除整个会话缓存。 它将删除所有存储在缓存中的实体。

让我们使用示例验证以上事实。

一级缓存检索示例

在此示例中,我使用 Hiberate 会话从数据库中检索DepartmentEntity对象。 我将多次检索它,并将观察 sql 日志以查看差异。

//Open the hibernate session
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();

//fetch the department entity from database first time
DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

//fetch the department entity again
department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

session.getTransaction().commit();
HibernateUtil.shutdown();

Output:

Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
Human Resource
Human Resource

如您所见,第二个“session.load()”语句不会再次执行select查询并直接加载部门实体

使用新会话的一级缓存检索示例

对于新会话,无论实体是否已存在于应用的任何其他会话中,都将从数据库中再次获取实体。

//Open the hibernate session
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();

Session sessionTemp = HibernateUtil.getSessionFactory().openSession();
sessionTemp.beginTransaction();
try
{
	//fetch the department entity from database first time
	DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
	System.out.println(department.getName());

	//fetch the department entity again
	department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
	System.out.println(department.getName());

	department = (DepartmentEntity) sessionTemp.load(DepartmentEntity.class, new Integer(1));
	System.out.println(department.getName());
}
finally
{
	session.getTransaction().commit();
	HibernateUtil.shutdown();

	sessionTemp.getTransaction().commit();
	HibernateUtil.shutdown();
}

Output:

Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
Human Resource
Human Resource

Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
Human Resource

您可以看到,即使部门实体存储在“session”对象中,当我们使用另一个会话对象“sessionTemp”时,仍然执行了另一个数据库查询。

从一级缓存中删除缓存对象的示例

尽管我们不能在 Hiberate 中禁用一级缓存,但是我们可以在需要时从其中删除一些对象。 这可以通过两种方法完成:

  • evct()
  • clear()

这里evict()用于从与会话关联的缓存中删除特定对象,clear()方法用于从会话关联的缓存中删除所有对象。 因此,它们本质上就像删除一个并删除所有。

//Open the hibernate session
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
try
{
	//fetch the department entity from database first time
	DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
	System.out.println(department.getName());

	//fetch the department entity again
	department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
	System.out.println(department.getName());

	session.evict(department);
	//session.clear(); 

	department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
	System.out.println(department.getName());
}
finally
{
	session.getTransaction().commit();
	HibernateUtil.shutdown();
}

Output:

Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
Human Resource
Human Resource

Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
Human Resource

显然,evict()方法从高速缓存中删除了部门对象,以便再次从数据库中获取它。

希望你喜欢上面的文章。 如果您有任何问题或建议,请发表评论。

学习愉快!

Hiberate 二级缓存如何工作?

原文: https://howtodoinjava.com/hibernate/how-hibernate-second-level-cache-works/

缓存是 ORM 框架提供的功能,可帮助用户获得快速运行的 Web 应用,同时帮助框架本身减少在单个事务中对数据库的查询数量。 Hibernate 还分两层提供此缓存功能。

  • 一级缓存:默认情况下启用,并在会话范围内工作。 阅读有关 Hiberate 一级缓存的更多信息。
  • 二级缓存:与一级缓存不同,后者可以在会话工厂范围内全局使用。

以上声明意味着,二级缓存是在会话工厂范围中创建的,并且可用于在使用该特定会话工厂创建的所有会话中使用。
这也意味着一旦关闭会话工厂,与之关联的所有缓存都将失效,并且缓存管理器也会关闭。
此外,这还意味着,如果您有两个会话工厂实例(通常没有应用这样做),则您的应用中将有两个缓存管理器,并且在访问存储在物理存储中的缓存时,您可能会得到不可预测的结果,例如缓存缺失。

hibernate first and second level cache_example

Hibernate 一级或二级缓存

在本教程中,我将提供有关 Hiberate 二级缓存的概念,并提供使用代码段的示例。

二级缓存的工作方式

让我们逐点写下所有事实:

  1. 每当 Hiberate 会话尝试加载实体时,它首先会在一级缓存(与特定的 Hiberate 会话关联)中寻找实体的缓存副本。
  2. 如果一级高速缓存中存在实体的高速缓存副本,则将其作为装入方法的结果返回。
  3. 如果第一级高速缓存中没有高速缓存的实体,则在第二级高速缓存中查找高速缓存的实体。
  4. 如果二级缓存已缓存实体,则将其作为装入方法的结果返回。 但是,在返回实体之前,它也存储在第一级缓存中,以便对实体的加载方法的下一次调用将从第一级缓存本身返回该实体,而无需再次进入第二级缓存。
  5. 如果在一级缓存和二级缓存中均未找到实体,则在作为load()方法的响应返回之前,将执行数据库查询并将实体存储在两个缓存级别中。
  6. 如果已通过 Hiberate 会话 API 完成了修改,则二级缓存会针对修改后的实体进行自我验证。
  7. 如果某些用户或进程直接在数据库中进行更改,则直到该缓存区域的“timeToLiveSeconds”持续时间过去之后,二级缓存才能更新自身。 在这种情况下,最好使整个缓存无效,然后让 Hiberate 再次构建其缓存。 您可以使用下面的代码片段使整个 Hiberate 二级缓存无效。
/**
 * Evicts all second level cache hibernate entites. This is generally only
 * needed when an external application modifies the databaase.
 */
public void evict2ndLevelCache() {
    try {
        Map<String, ClassMetadata> classesMetadata = sessionFactory.getAllClassMetadata();
        for (String entityName : classesMetadata.keySet()) {
            logger.info("Evicting Entity from 2nd level cache: " + entityName);
            sessionFactory.evictEntity(entityName);
        }
    } catch (Exception e) {
        logger.logp(Level.SEVERE, "SessionController", "evict2ndLevelCache", "Error evicting 2nd level hibernate cache entities: ", e);
    }
}

为了使用示例了解更多信息,我编写了一个测试应用,其中将 EhCache 配置为 2 级缓存。 让我们来看看各种场景:

a)实体是首次获取的

DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

System.out.println(HibernateUtil.getSessionFactory().getStatistics().getEntityFetchCount());           //Prints 1
System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());   //Prints 0

Output: 1 0

说明:实体不在第一级或第二级缓存中,因此从数据库中获取了该实体。

b)第二次获取实体

//Entity is fecthed very first time
DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

//fetch the department entity again
department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

System.out.println(HibernateUtil.getSessionFactory().getStatistics().getEntityFetchCount());           //Prints 1
System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());   //Prints 0

Output: 1 0

说明:实体存在于一级缓存中,因此从那里获取它。 无需转到二级缓存。

c)实体已从一级缓存中逐出并再次获取

//Entity is fecthed very first time
DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

//fetch the department entity again
department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

//Evict from first level cache
session.evict(department);

department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

System.out.println(HibernateUtil.getSessionFactory().getStatistics().getEntityFetchCount());           //Prints 1
System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());   //Prints 1

Output: 1 1

说明:首次从数据库中获取实体。 这导致它存储在第一级和第二级缓存中。 从第一级缓存获取第二个加载调用。 然后,我们从一级缓存中逐出实体。 因此,第三次load()调用转到第二级缓存,并且getSecondLevelCacheHitCount()返回 1。

d)从另一个会话访问二级缓存

//Entity is fecthed very first time
DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

//fetch the department entity again
department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

//Evict from first level cache
session.evict(department);

department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

department = (DepartmentEntity) anotherSession.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

System.out.println(HibernateUtil.getSessionFactory().getStatistics().getEntityFetchCount());           //Prints 1
System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());   //Prints 2

Output: 1 2

说明:当从同一会话工厂创建的另一个会话尝试获取实体时,将在二级缓存中成功查找该会话,并且不会进行数据库调用。

因此,现在我们清楚了 hibernate 如何使用二级缓存。

下载源码

如果有任何疑问或建议,请给我评论。

祝您学习愉快!

Hibernate EhCache 配置教程

原文: https://howtodoinjava.com/hibernate/hibernate-ehcache-configuration-tutorial/

缓存是 ORM 框架提供的功能,可帮助用户获得快速运行的 Web 应用,同时帮助框架本身减少在单个事务中对数据库的查询数量。 Hiberate 还在两层中提供了此缓存功能。

  • 一级缓存:默认情况下启用,并在会话范围内工作。 阅读有关 Hiberate 一级缓存的更多信息。
  • 二级缓存:与一级缓存不同,后者可以在会话工厂范围内全局使用。

在本教程中,我将给出一个使用 ehcache 配置作为 Hiberate 中的二级缓存的示例

Hibernate with EhCache

Hibernate 和 EhCache

Sections in this post: How second level cache works
About EhCache
Configuring EhCache
Configuring entity objects
Query caching
Example application
源码下载

二级缓存的工作方式

让我们逐点写下所有事实:

  1. 每当 Hiberate 会话尝试加载实体时,它首先会在一级缓存(与特定的 Hiberate 会话关联)中寻找实体的缓存副本。
  2. 如果一级高速缓存中存在实体的高速缓存副本,则将其作为装入方法的结果返回。
  3. 如果第一级高速缓存中没有高速缓存的实体,则在第二级高速缓存中查找高速缓存的实体。
  4. 如果二级缓存已缓存实体,则将其作为装入方法的结果返回。 但是,在返回实体之前,它也存储在第一级缓存中,以便对实体的加载方法的下一次调用将从第一级缓存本身返回该实体,而无需再次进入第二级缓存。
  5. 如果在一级缓存和二级缓存中均未找到实体,则在作为load()方法的响应返回之前,将执行数据库查询并将实体存储在两个缓存级别中。
  6. 如果已通过 Hiberate 会话 API 完成了修改,则二级缓存会针对修改后的实体进行自我验证。
  7. 如果某些用户或进程直接在数据库中进行更改,则直到该缓存区域的“timeToLiveSeconds”持续时间过去之后,二级缓存才能更新自身。 在这种情况下,最好使整个缓存无效,然后让 Hiberate 再次构建其缓存。 您可以使用下面的代码片段使整个 Hiberate 二级缓存无效。

关于 EhCache

Terracotta Ehcache 是​​一种流行的开源 Java 缓存,可以用作 Hibernate 二级缓存。 它可以用作独立的二级缓存,也可以配置为群集以提供复制的相干二级缓存。

Hibernate 随附 ehcache 库。 如果需要任何特定版本的 ehcache,请访问 Terracotta Ehcache 下载站点

http://www.terracotta.org/products/enterprise-ehcache

maven 依赖项适用于 Ehcache 2.0,并且任何升级都是:

<dependency>
	<groupId>net.sf.ehcache</groupId>
	<artifactId>ehcache</artifactId>
	<version>[2.0.0]</version>
	<type>pom</type>
</dependency>

配置 EhCache

要配置 ehcache,您需要执行两个步骤:

  1. 配置 Hibernate 进行二级缓存
  2. 指定二级缓存供应器

Hibernate 4.x 及更高版本

<property key="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

Hibernate 3.3 及更高版本

<property key="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">net.sf.ehcache.hibernate.EhCacheRegionFactory</property>

Hibernate 3.2 及更低版本

<property key="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</property>

配置实体对象

这可以以两种方式完成。

1)如果您正在使用hbm.xml文件,请使用以下配置:

<class name="com.application.entity.DepartmentEntity" table="...">
    <cache usage="read-write"/>
</class>

2)否则,如果您使用的是注解,请使用以下注解

@Entity
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY, 
region="department")
public class DepartmentEntity implements Serializable 
{
	//code
}

对于这两个选项,缓存策略可以具有以下类型:

  • none:不进行缓存。
  • readonly:如果您的应用需要读取而不是修改持久类的实例,则可以使用只读缓存。
  • read-write:如果应用需要更新数据,则可能需要读写缓存。
  • nonstrict-read-write :如果应用仅偶尔需要更新数据(即,如果两个事务很难同时尝试更新同一项目,则非常不可能),并且不需要严格的事务隔离 非严格读写缓存可能是合适的。
  • transactional :事务缓存策略为完全事务缓存供应器(例如 JBoss TreeCache)提供支持。 这样的缓存只能在 JTA 环境中使用,并且必须指定 hibernate.transaction.manager_lookup_class

查询缓存

您还可以启用查询缓存。 为此,请在hbm.xml中进行配置:

<property key="hibernate.cache.use_query_cache">true</property>

以及在代码中定义查询的位置,将方法调用setCacheable(true)添加到应缓存的查询中:

sessionFactory.getCurrentSession().createQuery("...").setCacheable(true).list();

默认情况下,Ehcache 将为您配置用于缓存的每个实体创建单独的缓存区域。 您可以通过将配置添加到ehcache.xml中来更改这些区域的默认值。 要提供此配置文件,请在 Hiberate 配置中使用以下属性:

<property name="net.sf.ehcache.configurationResourceName">/ehcache.xml</property>

并使用以下配置覆盖默认配置:

<cache
    name="com.somecompany.someproject.domain.Country"
    maxElementsInMemory="10000"
    eternal="false"
    timeToIdleSeconds="300"
    timeToLiveSeconds="600"
    overflowToDisk="true"
/>

请注意,在ehcache.xml中,如果eternal="true",那么我们不应编写timeToIdealSecondstimeToLiveSeconds,hibernate 会注意这些值
。因此,如果要手动提供值,最好始终使用eternal=”false”,以便我们可以手动将值分配给timeToIdealSecondstimeToLiveSeconds

timeToIdealSeconds="seconds"表示,如果全局缓存中的对象是理想的,则表示不被任何其他类或对象使用,那么它将等待一段时间,我们指定了该时间,如果时间已从全局缓存中删除 超过了timeToIdealSeconds值。

timeToLiveSeconds="seconds"表示另一个Session或类是否使用此对象,我的意思是它是否正在被其他会话使用,无论情况如何,一旦度过了timeToLiveSeconds指定的时间,然后它将被 Hiberate 从全局缓存中删除。

示例应用

在我们的示例应用中,我有一个DepartmentEntity,我想使用 ehcache 启用二级缓存。 让我们逐步记录更改:

1)hibernate.cfg.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernatedemo</property>
        <property name="hibernate.connection.password">password</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">true</property>
		<property name="hbm2ddl.auto">create</property>
		<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
        <mapping class="hibernate.test.dto.DepartmentEntity"></mapping>
    </session-factory>
</hibernate-configuration>

2)DepartmentEntity.java

package hibernate.test.dto;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

@Entity (name = "dept")
@Table(name = "DEPARTMENT", uniqueConstraints = {
		@UniqueConstraint(columnNames = "ID"),
		@UniqueConstraint(columnNames = "NAME") })

@Cache(usage=CacheConcurrencyStrategy.READ_ONLY, region="department")

public class DepartmentEntity implements Serializable {

	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	private Integer id;

	@Column(name = "NAME", unique = true, nullable = false, length = 100)
	private String name;

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

3)HibernateUtil.java

package hibernate.test;

import java.io.File;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;

public class HibernateUtil 
{
	private static final SessionFactory sessionFactory = buildSessionFactory();

    private static SessionFactory buildSessionFactory() 
    {
        try 
        {
            // Create the SessionFactory from hibernate.cfg.xml
            return new AnnotationConfiguration().configure(new File("hibernate.cgf.xml")).buildSessionFactory();
        }
        catch (Throwable ex) {
            // Make sure you log the exception, as it might be swallowed
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public static void shutdown() {
    	// Close caches and connection pools
    	getSessionFactory().close();
    }
}

4)TestHibernateEhcache.java

public class TestHibernateEhcache 
{	
	public static void main(String[] args) 
	{
		storeData();

		try
		{
			//Open the hibernate session
			Session session = HibernateUtil.getSessionFactory().openSession();
			session.beginTransaction();

			//fetch the department entity from database first time
			DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
			System.out.println(department.getName());

			//fetch the department entity again; Fetched from first level cache
			department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
			System.out.println(department.getName());

			//Let's close the session
			session.getTransaction().commit();
			session.close();

			//Try to get department in new session
			Session anotherSession = HibernateUtil.getSessionFactory().openSession();
			anotherSession.beginTransaction();

			//Here entity is already in second level cache so no database query will be hit
			department = (DepartmentEntity) anotherSession.load(DepartmentEntity.class, new Integer(1));
			System.out.println(department.getName());

			anotherSession.getTransaction().commit();
			anotherSession.close();
		}
		finally
		{
			System.out.println(HibernateUtil.getSessionFactory().getStatistics().getEntityFetchCount()); //Prints 1
			System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount()); //Prints 1

			HibernateUtil.shutdown();
		}
	}

	private static void storeData()
	{
		Session session = HibernateUtil.getSessionFactory().openSession();
		session.beginTransaction();

		DepartmentEntity department = new DepartmentEntity();
		department.setName("Human Resource");

		session.save(department);
		session.getTransaction().commit();
	}
}

Output:

Hibernate: insert into DEPARTMENT (NAME) values (?)
Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
Human Resource
Human Resource
Human Resource
1
1

在上面的输出中,第一次从数据库中获取部门。 但接下来的两次是从缓存中提取的。 最后一次获取来自二级缓存。

要下载上述应用的源代码,请点击以下链接。

源码下载

希望你喜欢上面的文章。 如果您有任何问题或建议,请发表评论。

祝您学习愉快!

参考

http://www.ehcache.org/documentation/user-guide/hibernate

https://community.jboss.org/wiki/ConfigureEhcacheAsASeecondLevelCache

Hibernate OSCache 配置示例教程

原文: https://howtodoinjava.com/hibernate/hibernate-oscache-configuration-example-tutorial/

OSCache 是 OpenSymphony 开发的 Java 框架,可轻松在 Web 应用中缓存内容。 使用 Hiberate ,可以将其配置为充当二级缓存

在我的前一篇文章中,我们了解了为 Hiberate 配置 EhCache 的过程,这是 Hiberate 中的默认二级缓存。 在本文中,我以使用 Hiberate 配置 OSCache 为例。

Sections in this post: 
Runtime dependencies
Hibernate configuration
In memory cache example
Physical cache example

运行时依赖项

我已经使用 Maven 来管理项目依赖项,并且pom.xml文件中的必要补充是:

<repositories>
	<repository>
	  <id>repository.jboss.org-public</id>
	  <name>JBoss.org Maven repository</name>
	  <url>https://repository.jboss.org/nexus/content/groups/public</url>
	</repository>  
</repositories>

<!-- OSCache dependencies -->
<dependency>
	<groupId>opensymphony</groupId>
	<artifactId>oscache</artifactId>
	<version>2.4.1</version>
</dependency>
<dependency>
  <groupId>javax.jms</groupId>
  <artifactId>jms</artifactId>
  <version>1.1</version>
</dependency>

如果您不使用 maven,则在项目构建路径中添加相应的 jar 文件

Hiberate 配置

在项目中配置 OSCache 的唯一更改是,必须在 Hiberate 配置文件中创建 hibernate.cfg.xml文件:

	<!-- Cache provider class -->
	<property name="hibernate.cache.provider_class">org.hibernate.cache.OSCacheProvider</property>

内存中的缓存示例

这是默认实现,如果未配置物理缓存属性,则会得到它。 让我们看一下测试代码示例:

	try
	{
		//Open the hibernate session
		Session session = HibernateUtil.getSessionFactory().openSession();
		session.beginTransaction();

		//fetch the department entity from database first time
		DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
		System.out.println(department.getName());

		//fetch the department entity again; Fetched from first level cache
		department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
		System.out.println(department.getName());

		//Let's close the session
		session.getTransaction().commit();
		session.close();

		//Try to get department in new session
		Session anotherSession = HibernateUtil.getSessionFactory().openSession();
		anotherSession.beginTransaction();

		//Here entity is already in second level cache so no database query will be hit
		department = (DepartmentEntity) anotherSession.load(DepartmentEntity.class, new Integer(1));
		System.out.println(department.getName());

		anotherSession.getTransaction().commit();
		anotherSession.close();
	}
	finally
	{
		System.out.println(HibernateUtil.getSessionFactory().getStatistics().getEntityFetchCount()); //Prints 1
		System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount()); //Prints 1

		HibernateUtil.shutdown();
	}

	Output in console:

	Hibernate: insert into DEPARTMENT (NAME) values (?)
	Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
	Human Resource
	Human Resource
	Human Resource
	1
	1

您将一次又一次地获得此输出,因为每次关闭 Hiberate 时,都会刷新内存并在下次运行时构建二级缓存。

物理缓存示例

如果您要构建的缓存非常大,则最好在文件系统中构建它(例如 Windows 中的 C 驱动器)。 这将防止 Hiberate 在每次重新启动应用时建立高速缓存。 同样,二级缓存提取规则仍然适用。

要启用物理缓存,请下载oscache.properties文件,并取消注释以下几行:

cache.memory=false
cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.DiskPersistenceListener
cache.path=c:/temp/cache
cache.algorithm=com.opensymphony.oscache.base.algorithm.LRUCache

让我们看一下测试代码示例:

	try
	{
		//Open the hibernate session
		Session session = HibernateUtil.getSessionFactory().openSession();
		session.beginTransaction();

		//fetch the department entity from database first time
		DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
		System.out.println(department.getName());

		//fetch the department entity again; Fetched from first level cache
		department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
		System.out.println(department.getName());

		//Let's close the session
		session.getTransaction().commit();
		session.close();

		//Try to get department in new session
		Session anotherSession = HibernateUtil.getSessionFactory().openSession();
		anotherSession.beginTransaction();

		//Here entity is already in second level cache so no database query will be hit
		department = (DepartmentEntity) anotherSession.load(DepartmentEntity.class, new Integer(1));
		System.out.println(department.getName());

		anotherSession.getTransaction().commit();
		anotherSession.close();
	}
	finally
	{
		System.out.println(HibernateUtil.getSessionFactory().getStatistics().getEntityFetchCount()); //Prints 1
		System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount()); //Prints 1

		HibernateUtil.shutdown();
	}

	Output in console:

	Hibernate: insert into DEPARTMENT (NAME) values (?)
	Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
	Human Resource
	Human Resource
	Human Resource
	1
	1

上面的代码将在“c:/tempcache”位置创建物理缓存。

physical oscache example

下次再次运行示例代码时,将获得以下输出:

	Hibernate: insert into DEPARTMENT (NAME) values (?)
	Human Resource
	Human Resource
	Human Resource
	0
	2

将部门实体存储在物理二级缓存中并从那里获取的原因很简单。 因此 Hiberate 将不会再次进入数据库。

要下载该项目的源代码,请遵循给定的链接。

祝您学习愉快!

Hibernate C3P0 连接池配置教程

原文: https://howtodoinjava.com/hibernate/hibernate-c3p0-connection-pool-configuration-tutorial/

默认情况下,Hibernate 使用 JDBC 连接以便与数据库进行交互。 创建这些连接非常昂贵 - Hibernate 在典型的使用情况下可能会执行最昂贵的单个操作。 由于 JDBC 连接管理非常昂贵,因此您可能会建议您使用连接池,它可以提前打开连接(并仅在需要时关闭它们,而不是“不再使用时”)。

幸运的是,默认情况下,Hibernate 设计为使用连接池(内部实现)。 但是,Hibernate 的内置连接池并非设计用于生产用途。 在生产中,将通过使用 JNDI 提供的数据库连接或通过参数和类路径配置的外部连接池来使用外部连接池。

C3P0 是外部连接池的示例。 在本教程中,我们将学习如何将其与 hibernate 一起使用。

Table of Contents

1) Maven dependencies
2) Configure C3P0 Connection Pool with Hibernate 
3) Test connection pooling in runtime

1)Maven 依赖

要使用 Hiberate 配置 c3p0,我们需要在pom.xml中添加 c3p0 和 Hibernate 的 c3p0 连接供应器作为依赖项。 请注意,hibernate-c3p0依赖项的版本应与 Hibernate 的兼容版本匹配。

以下配置使用了最新版本的 hibernate 和 c3p0。

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-c3p0</artifactId>
    <version>4.3.6.Final</version>
</dependency>
<dependency>
	<groupId>c3p0</groupId>
	<artifactId>c3p0</artifactId>
	<version>0.9.1.2</version>
</dependency>

2)使用 Hiberate 配置 C3P0 连接池

最好的部分是,使用 Hiberate 配置 C3P0 确实非常容易。 只需在hibernate.cfg.xml文件中添加任何 c3p0 属性即可。 例如在hibernate.cfg.xml中添加以下条目。

<property name="hibernate.c3p0.min_size">10</property>

恭喜!! 现在,使用您的应用的 Hiberate 层配置了 C3P0 的连接池。 您很好地开始测试这些东西。 真的很简单,不是吗?

可以使用hibernate.cfg.xml中的以下属性来设置相当详细的配置。

<property name="hibernate.c3p0.min_size">10</property>
<property name="hibernate.c3p0.max_size">20</property>
<property name="hibernate.c3p0.acquire_increment">1</property>
<property name="hibernate.c3p0.idle_test_period">3000</property>
<property name="hibernate.c3p0.max_statements">50</property>
<property name="hibernate.c3p0.timeout">1800</property>

您可以在官方文档中找到有关上述配置开关的详细信息。

3)在运行时测试连接池

不带 C3P0 连接池配置

在未配置 C3P0 的情况下,如果您看到 Hiberate 的调试日志,则会看到以下内容:

DEBUG Configuration:1841 - Preparing to build session factory with filters : {}
WARN DriverManagerConnectionProviderImpl:93 - HHH000402: Using Hibernate built-in connection pool (not for production use!)
INFO DriverManagerConnectionProviderImpl:166 - HHH000401: using driver [org.hsqldb.jdbcDriver] at URL [jdbc:hsqldb:mem:howtodoinjava]
INFO DriverManagerConnectionProviderImpl:172 - HHH000046: Connection properties: {user=sa, password=}
INFO DriverManagerConnectionProviderImpl:180 - HHH000006: Autocommit mode: false
INFO DriverManagerConnectionProviderImpl:102 - HHH000115: Hibernate connection pool size: 20 (min=1)
DEBUG DriverManagerConnectionProviderImpl:104 - Initializing Connection pool with 1 Connections
...
...
...
EBUG JdbcTransaction:113 - committed JDBC Connection
DEBUG SessionFactoryImpl:1339 - HHH000031: Closing
DEBUG AbstractServiceRegistryImpl:406 - Implicitly destroying ServiceRegistry on de-registration of all child ServiceRegistries
INFO DriverManagerConnectionProviderImpl:281 - HHH000030: Cleaning up connection pool [jdbc:hsqldb:mem:howtodoinjava]

使用 C3P0 连接池配置

配置 C3P0 连接池后,您将在日志中看到现在已从 C3P0 连接池本身获取连接。

DEBUG Configuration:1841 - Preparing to build session factory with filters : {}
 INFO C3P0ConnectionProvider:133 - HHH010002: C3P0 using driver: org.hsqldb.jdbcDriver at URL: jdbc:hsqldb:mem:howtodoinjava
 INFO C3P0ConnectionProvider:134 - HHH000046: Connection properties: {user=sa, password=****}
 INFO C3P0ConnectionProvider:137 - HHH000006: Autocommit mode: false
 INFO MLog:92 - MLog clients using log4j logging.
 INFO C3P0Registry:216 - Initializing c3p0-0.9.2.1 [built 20-March-2013 10:47:27 +0000; debug? true; trace: 10]
DEBUG DynamicPooledDataSourceManagerMBean:258 - MBean: com.mchange.v2.c3p0:type=PooledDataSource,identityToken=19tu9of94ho8s13xij3fm|34e475e1,name=19tu9of94ho8s13xij3fm|34e475e1 registered.
DEBUG DynamicPooledDataSourceManagerMBean:253 - MBean: com.mchange.v2.c3p0:type=PooledDataSource,identityToken=19tu9of94ho8s13xij3fm|34e475e1,name=19tu9of94ho8s13xij3fm|34e475e1 unregistered, in order to be reregistered after update.
DEBUG DynamicPooledDataSourceManagerMBean:258 - MBean: com.mchange.v2.c3p0:type=PooledDataSource,identityToken=19tu9of94ho8s13xij3fm|34e475e1,name=19tu9of94ho8s13xij3fm|34e475e1 registered.
 INFO AbstractPoolBackedDataSource:522 - Initializing c3p0 pool... com.mchange.v2.c3p0.PoolBackedDataSource@d29bbf50 [ connectionPoolDataSource -> com.mchange.v2.c3p0.WrapperConnectionPoolDataSource@a9bbd924 [ ... ]
 ...
 ...
 ...
 DEBUG ActiveManagementCoordinator:97 - C3P0Registry mbean unregistered.
DEBUG BasicResourcePool:1022 - Preparing to destroy resource: com.mchange.v2.c3p0.impl.NewPooledConnection@1d1fcfbb
DEBUG C3P0PooledConnectionPool:616 - Preparing to destroy PooledConnection: com.mchange.v2.c3p0.impl.NewPooledConnection@1d1fcfbb
DEBUG AbstractPoolBackedDataSource:477 - com.mchange.v2.c3p0.PoolBackedDataSource@34e475e1 has been closed. 
java.lang.Exception: DEBUG STACK TRACE for PoolBackedDataSource.close().
	at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.close(AbstractPoolBackedDataSource.java:477)
	at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.close(AbstractPoolBackedDataSource.java:489)
	at com.mchange.v2.c3p0.DataSources.destroy(DataSources.java:372)
	at com.mchange.v2.c3p0.DataSources.destroy(DataSources.java:348)
	at org.hibernate.c3p0.internal.C3P0ConnectionProvider.stop(C3P0ConnectionProvider.java:258)
	at org.hibernate.service.internal.AbstractServiceRegistryImpl.stopService(AbstractServiceRegistryImpl.java:377)
	at org.hibernate.service.internal.AbstractServiceRegistryImpl.destroy(AbstractServiceRegistryImpl.java:361)
	at org.hibernate.service.internal.AbstractServiceRegistryImpl.deRegisterChild(AbstractServiceRegistryImpl.java:410)
	at org.hibernate.service.internal.AbstractServiceRegistryImpl.destroy(AbstractServiceRegistryImpl.java:368)
	at org.hibernate.internal.SessionFactoryImpl.close(SessionFactoryImpl.java:1377)
	at com.howtodoinjava.demo.util.HibernateUtil.shutdown(HibernateUtil.java:39)
	at com.howtodoinjava.test.TestHibernate.main(TestHibernate.java:22)

这就是这个简单而有用的教程,有关如何使用 Hiberate 配置 C3P0 连接池。

祝您学习愉快!

参考http://www.mchange.com/projects/c3p0/#configuration

Hiberate 内存数据库

原文: https://howtodoinjava.com/hibernate/hibernate-4-using-in-memory-database-with-hibernate/

在我们工作的组织中,朋友经常不允许甚至访问必要的开发人员工具,例如本地数据库安装。 这也可能是由于相当正当的原因(有时似乎只是荒谬的)。 在此 Hiberate 内存数据库教程中,我提供了一个无需安装任何数据库即可测试您的 Hiberate 代码的示例。

这可以帮助您编写应用的单元测试用例,因为某些架构师将数据库访问视为依赖。

1. 内存数据库实现

1.1 Maven 依赖

<dependency>  
    <groupId>hsqldb</groupId>  
    <artifactId>hsqldb</artifactId>  
    <version>1.8.0.10</version>  
</dependency>

1.2 获取数据库连接

对于此示例,我使用 HSQLDB 数据库通过我们的 Hiberate 代码创建和访问内存数据库。 如果您使用简单的简单 JDBC,则可以直接使用下面的语句访问内存数据库。

Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:howtodoinjava", "sa", "");

这里要注意的主要事情是“mem:howtodoinjava”。 这里mem表示使用内存数据库,而不是任何物理数据库。

1.3 需要默认的用户名和密码

请注意,尽管您将必须提供默认的用户名“sa”和一个空白密码才能连接到数据库,否则将出现以下异常。

Caused by: java.sql.SQLException: Access is denied
	at org.hsqldb.jdbc.Util.sqlException(Unknown Source)
	at org.hsqldb.jdbc.jdbcConnection.<init>(Unknown Source)
	at org.hsqldb.jdbcDriver.getConnection(Unknown Source)
	at org.hsqldb.jdbcDriver.connect(Unknown Source)
	at org.hibernate.engine.jdbc.connections.internal.DriverConnectionCreator.makeConnection(DriverConnectionCreator.java:55)
	... 15 more

2. Hibernate 内存数据库示例

现在,在下面的源代码文件中查找使用 hibernate 进行内存数据库访问的示例。

2.1 hibernate.cfg.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
       	<property name="hibernate.archive.autodetection">class,hbm</property>  
       	<property name="hibernate.dialect">org.hibernate.dialect.HSQLDialect</property>  
       	<property name="hibernate.show_sql">true</property>    
       	<property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property>    
       	<property name="hibernate.connection.username">sa</property>    
       	<property name="hibernate.connection.password"></property>    
       	<property name="hibernate.connection.url">jdbc:hsqldb:mem:howtodoinjava</property>    
       	<property name="hibernate.hbm2ddl.auto">create</property>    
       	<mapping class="com.howtodoinjava.demo.entity.EmployeeEntity"></mapping>
    </session-factory>
</hibernate-configuration>

2.2. HibernateUtil.java

package com.howtodoinjava.demo.util;

import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;

public class HibernateUtil
{
   private static SessionFactory sessionFactory = buildSessionFactory();

   private static SessionFactory buildSessionFactory()
   {
      try
      {
         if (sessionFactory == null)
         {
            Configuration configuration = new Configuration().configure(HibernateUtil.class.getResource("/hibernate.cfg.xml"));
            StandardServiceRegistryBuilder serviceRegistryBuilder = new StandardServiceRegistryBuilder();
            serviceRegistryBuilder.applySettings(configuration.getProperties());
            ServiceRegistry serviceRegistry = serviceRegistryBuilder.build();
            sessionFactory = configuration.buildSessionFactory(serviceRegistry);
         }
         return sessionFactory;
      } catch (Throwable ex)
      {
         System.err.println("Initial SessionFactory creation failed." + ex);
         throw new ExceptionInInitializerError(ex);
      }
   }

   public static SessionFactory getSessionFactory()
   {
      return sessionFactory;
   }

   public static void shutdown()
   {
      getSessionFactory().close();
   }
}

2.3. EmployeeEntity.java

package com.howtodoinjava.demo.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@org.hibernate.annotations.Entity(dynamicUpdate = true)
@Table(name = "Employee", uniqueConstraints = {@UniqueConstraint(columnNames = "ID"), @UniqueConstraint(columnNames = "EMAIL")})
public class EmployeeEntity implements Serializable
{
   private static final long serialVersionUID = -1798070786993154676L;
   @Id
   @Column(name = "ID", unique = true, nullable = false)
   private Integer           employeeId;
   @Column(name = "EMAIL", unique = true, nullable = false, length = 100)
   private String            email;
   @Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
   private String            firstName;
   @Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
   private String            lastName;

   public Integer getEmployeeId()
   {
      return employeeId;
   }

   public void setEmployeeId(Integer employeeId)
   {
      this.employeeId = employeeId;
   }

   public String getEmail()
   {
      return email;
   }

   public void setEmail(String email)
   {
      this.email = email;
   }

   public String getFirstName()
   {
      return firstName;
   }

   public void setFirstName(String firstName)
   {
      this.firstName = firstName;
   }

   public String getLastName()
   {
      return lastName;
   }

   public void setLastName(String lastName)
   {
      this.lastName = lastName;
   }
}

现在测试上面的代码。

2.4. TestHibernate.java

package com.howtodoinjava.test;

import org.hibernate.Session;

import com.howtodoinjava.demo.entity.EmployeeEntity;
import com.howtodoinjava.demo.util.HibernateUtil;

public class TestHibernate
{
   public static void main(String[] args)
   {
      Session session = HibernateUtil.getSessionFactory().openSession();
      session.beginTransaction();
      // Add new Employee object
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setEmail("[email protected]");
      emp.setFirstName("demo");
      emp.setLastName("user");
      session.save(emp);
      session.getTransaction().commit();
      HibernateUtil.shutdown();
   }
}

Output:

Hibernate: drop table Employee if exists
Hibernate: create table Employee (ID integer not null, EMAIL varchar(100) not null, FIRST_NAME varchar(100) not null, LAST_NAME varchar(100) not null, primary key (ID))
Hibernate: alter table Employee add constraint UK_ardf0f11mfa6tujs3hflthwdv  unique (EMAIL)
Hibernate: insert into Employee (EMAIL, FIRST_NAME, LAST_NAME, ID) values (?, ?, ?, ?)

3. 项目结构和依赖项

以下是此示例的项目结构。

hibernate-hsqldb-in-memory-database-example

如果在设置本示例的 maven 依赖项方面需要任何帮助,请遍历pom.xml文件。

3.1 pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd;
  <modelVersion>4.0.0</modelVersion>
  <groupId>ABC</groupId>
  <artifactId>ABC</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
  <build>
    <sourceDirectory>src</sourceDirectory>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.7</source>
          <target>1.7</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
  	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-core</artifactId>
		<version>4.3.6.Final</version>
	</dependency>
	<!-- for JPA, use hibernate-entitymanager instead of hibernate-core -->
	<dependency>
	    <groupId>org.hibernate</groupId>
	    <artifactId>hibernate-entitymanager</artifactId>
	    <version>4.3.6.Final</version>
	</dependency>
	<!-- optional -->
	<dependency>
	    <groupId>org.hibernate</groupId>
	    <artifactId>hibernate-osgi</artifactId>
	    <version>4.3.6.Final</version>
	</dependency>
	<dependency>
	    <groupId>org.hibernate</groupId>
	    <artifactId>hibernate-envers</artifactId>
	    <version>4.3.6.Final</version>
	</dependency>
	<dependency>
	    <groupId>org.hibernate</groupId>
	    <artifactId>hibernate-c3p0</artifactId>
	    <version>4.3.6.Final</version>
	</dependency>
	<dependency>
	    <groupId>org.hibernate</groupId>
	    <artifactId>hibernate-proxool</artifactId>
	    <version>4.3.6.Final</version>
	</dependency>
	<dependency>
	    <groupId>org.hibernate</groupId>
	    <artifactId>hibernate-infinispan</artifactId>
	    <version>4.3.6.Final</version>
	</dependency>
	<dependency>
	    <groupId>org.hibernate</groupId>
	    <artifactId>hibernate-ehcache</artifactId>
	    <version>4.3.6.Final</version>
	</dependency>
  	<dependency>
	    <groupId>antlr</groupId>
	    <artifactId>antlr</artifactId>
	    <version>2.7.6</version>
	</dependency>
	<dependency>
	    <groupId>commons-collections</groupId>
	    <artifactId>commons-collections</artifactId>
	    <version>3.1</version>
	</dependency>
	<dependency>
	    <groupId>dom4j</groupId>
	    <artifactId>dom4j</artifactId>
	    <version>1.6.1</version>
	</dependency>
	<dependency>
	    <groupId>javassist</groupId>
	    <artifactId>javassist</artifactId>
	    <version>3.4.GA</version>
	</dependency>
	<dependency>
	    <groupId>javax.transaction</groupId>
	    <artifactId>jta</artifactId>
	    <version>1.1</version>
	</dependency>
	<dependency>
	    <groupId>org.slf4j</groupId>
	    <artifactId>slf4j-api</artifactId>
	    <version>1.5.6</version>
	</dependency>
	<dependency>
	    <groupId>org.slf4j</groupId>
	    <artifactId>slf4j-log4j12</artifactId>
	    <version>1.5.6</version>
	</dependency>
	<dependency>  
    	<groupId>hsqldb</groupId>  
	    <artifactId>hsqldb</artifactId>  
	    <version>1.8.0.10</version>  
	</dependency>
  </dependencies>
</project>

这就是有关将内存数据库与 Hibernate 结合使用的快速教程的全部内容。

学习愉快!

Hibernate 验证器 – Java Bean 验证示例

原文: https://howtodoinjava.com/hibernate/hibernate-validator-java-bean-validation/

使用 Hiberate 验证器验证 Java bean 字段的 Java 示例。 Bean 验证 API 2JSR-380)提供了一些流行的注解,可以将这些注解附加到每个 Bean 属性,以保持数据完整性。

1. Maven 依赖

以下是必需的 Hiberate 验证器 maven 依赖项

Bean 验证允许错误消息中包含表达式。 要解析这些表达式,我们必须添加对表达式语言 API 以及该 API 实现的依赖。 我使用 Jboss 的javax.el中实现的javax.el-api规范添加了它们。

<!-- Java bean validation API - Spec -->
<dependency>
	<groupId>javax.validation</groupId>
	<artifactId>validation-api</artifactId>
	<version>2.0.1.Final</version>
</dependency>

<!-- Hibernate validator - Bean validation API Implementation -->
<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-validator</artifactId>
	<version>6.0.11.Final</version>
</dependency>

<!-- Verify validation annotations usage at compile time -->
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-validator-annotation-processor</artifactId>
  <version>6.0.11.Final</version>
</dependency>

<!-- Unified Expression Language - Spec -->
<dependency>
	<groupId>javax.el</groupId>
	<artifactId>javax.el-api</artifactId>
	<version>3.0.1-b06</version>
</dependency>

<!-- Unified Expression Language - Implementation -->
<dependency>
	<groupId>org.glassfish.web</groupId>
	<artifactId>javax.el</artifactId>
	<version>2.2.6</version>
</dependency>

2. Hibernate 验证器示例

2.1 带有验证注解的模型

package com.howtodoinjava.example.model;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class User {

    @NotNull(message = "Please enter id")
    private Long id;

    @Size(max = 20, min = 3, message = "{user.name.invalid}")
    @NotEmpty(message = "Please enter name")
    private String name;

    @Email(message = "{user.email.invalid}")
    @NotEmpty(message = "Please enter email")
    private String email;

    public User(Long id, String name, String email) {
        super();
        this.id = id;
        this.name = name;
        this.email = email;
    }

    //Setters and Getters
}

2.2 消息属性

默认情况下,所有消息都是从类路径中的ValidationMessages.properties文件解析的。 如果文件不存在,则不会发生消息解析

user.name.invalid=Invalid Username
user.email.invalid=Invalid Email

2.3 执行验证

现在,让我们在User实例上执行 bean 验证。

package com.howtodoinjava.example;

import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import com.howtodoinjava.example.model.User;

public class TestHibernateValidator 
{
    public static void main(String[] args) 
    {
        //Create ValidatorFactory which returns validator
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();

        //It validates bean instances
        Validator validator = factory.getValidator();

        User user = new User(null, "1", "abcgmail.com");

        //Validate bean
        Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);

        //Show errors
        if (constraintViolations.size() > 0) {
            for (ConstraintViolation<User> violation : constraintViolations) {
                System.out.println(violation.getMessage());
            }
        } else {
            System.out.println("Valid Object");
        }
    }
}

程序输出:

Please enter id
Invalid Email
Invalid Username

3. 自定义资源包

默认情况下,框架从类路径中的ValidationMessages.properties文件中提取验证消息。 您可以如下配置自己的自定义属性文件

将这两个文件放在类路径中,即messages.propertiesotherMessages.properties。 现在,将从这两个属性文件解析所有消息。

Validator validator = Validation.byDefaultProvider()
        .configure()
        .messageInterpolator(
                new ResourceBundleMessageInterpolator(
                        new AggregateResourceBundleLocator(
                                Arrays.asList(
                                        "messages",
                                        "otherMessages"
                                )
                        )
                )
        )
        .buildValidatorFactory()
        .getValidator();

4. 消息插值

4.1 邮件参数解析

在消息解析期间,可以使用运行时值使验证消息更有意义。 消息中的参数值解析以两种方式发生:

  1. 要解析注解属性中的值,只需将大括号括起来即可。 例如。 {min}{max}
  2. 要解析字段的运行时值,请使用占位符${validatedValue}

4.2)参数解析示例

  • 带有消息和参数的属性文件。

    user.name.invalid='${validatedValue}' is an invalid name. It must be minimum {min} chars and maximum {max} chars.
    
    
  • 带有验证注解的 Bean 字段。

    @Size(max = 20, min = 3, message = "{user.name.invalid}")
    private String name;
    
    
  • 程序输出中已解决的消息。

    User user = new User(23l, "xy", "[email protected]");
    Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
    
    //Output
    
    'xy' is an invalid name. It must be minimum 3 chars and maximum 20 chars.
    
    

5. Hiberate 验证器注解

现在,当我们知道如何以编程方式使用 Hiberate 验证器时。 让我们看一下可以在 bean 类中使用的所有注解。

5.1 Bean 验证注解

注解 描述
@Digits(integer=, fraction=) 检查注解的值是否为最多integer位和fractional小数位的数字。
@Email 检查指定的字符序列是否为有效的电子邮件地址。
@Max(value=) 检查带注解的值是否小于或等于指定的最大值。
@Min(value=) 检查带注解的值是否大于或等于指定的最小值
@NotBlank 检查带注解的字符序列不为空,并且修剪后的长度大于 0。
@NotEmpty 检查带注解的元素是否不为null或为空。
@Null 检查带注解的值是否为空
@NotNull 检查带注解的值不为空
@Pattern(regex=, flags=) 考虑给定的flag匹配项,检查带注解的字符串是否与正则表达式regex匹配
@Size(min=, max=) 检查带注解的元素的大小是否在最小和最大(包括)之间
@Negative 检查元素是否严格为负。 零值被视为无效。
@NegativeOrZero 检查元素是负数还是零。
@Future 检查带注解的日期是否是将来的日期。
@FutureOrPresent 检查带注解的日期是现在还是将来。
@PastOrPresent 检查带注解的日期是过去还是现在。

5.2 Hiberate 验证器特定的注解

除了 Bean 验证 API 定义的约束之外,Hibernate 验证器还提供了以下有用的自定义约束。

注解 描述
@CreditCardNumber( ignoreNonDigitCharacters=) 检查带注解的字符序列是否通过了 Luhn 校验和测试。 请注意,此验证旨在检查用户错误,而不是信用卡有效性!
@Currency(value=) 检查带注解的javax.money.MonetaryAmount的货币单位是否为指定货币单位的一部分。
@EAN 检查带注解的字符序列是否为有效的 EAN 条形码。 默认值为 EAN-13。
@ISBN 检查带注解的字符序列是否为有效的 ISBN
@Length(min=, max=) 验证带注解的字符序列是否在minmax之间。
@Range(min=, max=) 检查带注解的值是否介于(包括)指定的最小值和最大值之间。
@UniqueElements 检查带注解的集合仅包含唯一元素。
@URL 根据 RFC2396 检查带注解的字符序列是否为有效 URL。

将我的问题放在评论部分。

学习愉快!

参考文献:

Bean 验证 2.0

Hiberate 验证器注解处理器

Hibernate 验证器 CDI – @HibernateValidator示例

原文: https://howtodoinjava.com/hibernate/hibernate-validator-cdi/

注入默认 Java bean 验证类实现的 Hiberate 验证器 CDI 的 Java 示例。 javax.validation.ValidatorFactoryjavax.validation.Validator具有hibernate-validator-cdi依赖项。 如果应用具有多个org.hibernate.validator.cdi.HibernateValidator之类的验证器实现,还应该学习注入专门的验证器。

请注意,如果您的应用在开箱即用的环境中运行,那么您无需添加其他依赖项即可运行此示例。 例如,Spring 框架隐式提供了此类 CDI 基础结构,因此请不要在 Spring 框架中使用 Hiberate 验证器 CDI

对于其他应用,例如独立的 Java 应用,可能需要它才能使用注解创建HibernateValidator

1. Hibernate 验证器 CDI Maven 依赖项

下面列出了与 Hiberate 验证器一起使用的所有必需依赖项。

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd;
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.howtodoinjava.example</groupId>
	<artifactId>hibernate-validator</artifactId>
	<version>0.0.1</version>
	<packaging>jar</packaging>

	<name>hibernate-validator</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>javax.inject</groupId>
			<artifactId>javax.inject</artifactId>
			<version>1</version>
		</dependency>
		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>2.0.1.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate.validator</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>6.0.11.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate.validator</groupId>
			<artifactId>hibernate-validator-cdi</artifactId>
			<version>6.0.11.Final</version>
		</dependency>
		<dependency>
			<groupId>javax.el</groupId>
			<artifactId>javax.el-api</artifactId>
			<version>3.0.1-b06</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.web</groupId>
			<artifactId>javax.el</artifactId>
			<version>2.2.6</version>
		</dependency>
	</dependencies>
</project>

2. 带有验证注解的模型类

一个带有字段验证注解的简单 Java 类。

package com.howtodoinjava.example.model;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class User {

    @NotNull(message = "Please enter id")
    private Long id;

    @Size(max = 20, min = 3, message = "{user.name.invalid}")
    @NotEmpty(message = "Please enter name")
    private String name;

    @Email(message = "{user.email.invalid}")
    @NotEmpty(message = "Please enter email")
    private String email;

    public User(Long id, String name, String email) {
        super();
        this.id = id;
        this.name = name;
        this.email = email;
    }

	//Getters and Setters

    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + ", email=" + email + "]";
    }
}

3. 注入默认的和 Hiberate javax.validation.Validator

使用@Inject注解注入ValidatorFactoryValidator。 注入的 Bean 是默认的验证器工厂实例和验证器实例。

@Inject
private static ValidatorFactory validatorFactory;

@Inject
private static Validator validator;

如果您正在使用多个 Bean 验证供应器,则可以通过使用@HibernateValidator限定符注解注入点来确保注入了 Hibernate 验证器的工厂和验证器。

import org.hibernate.validator.cdi.HibernateValidator;

@Inject
@HibernateValidator
private static ValidatorFactory validatorFactory;

@Inject
@HibernateValidator
private static Validator validator;

4. 如何验证 bean

使用注入的验证器验证 Java Bean 和检查错误消息的示例。

package com.howtodoinjava.example;

import java.util.Set;

import javax.inject.Inject;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import org.hibernate.validator.cdi.HibernateValidator;

import com.howtodoinjava.example.model.User;

public class TestHibernateValidator 
{
    @Inject
    @HibernateValidator
    private static ValidatorFactory validatorFactory;

    @Inject
    @HibernateValidator
    private static Validator validator;

    public static void main(String[] args) 
    {
        //Create ValidatorFactory which returns validator
        validatorFactory = Validation.buildDefaultValidatorFactory();

        //It validates bean instances
        validator = validatorFactory.getValidator();

        User user = new User(null, "1", "abcgmail.com");

        //Validate bean
        Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);

        //Show errors
        if (constraintViolations.size() > 0) {
            for (ConstraintViolation<User> violation : constraintViolations) {
                System.out.println(violation.getMessage());
            }
        } else {
            System.out.println("Valid Object");
        }
    }
}

程序输出:

Aug 06, 2018 12:25:17 PM org.hibernate.validator.internal.util.Version <clinit>
INFO: HV000001: Hibernate Validator 6.0.11.Final

Please enter id
'1' is an invalid name. It must be minimum 3 chars and maximum 20 chars.
Invalid Email

上面的示例从类路径中的属性文件中获取资源消息

user.name.invalid='${validatedValue}' is an invalid name. It must be minimum {min} chars and maximum {max} chars.
user.email.invalid=Invalid Email

5. 总结

在上面的示例中,我们学习以下内容:

  • 我们如何包含 Hiberate 验证器 CDI 依赖项并使用它。
  • 如何注入默认的验证器工厂和验证器实例。
  • 如果存在多个 Java Bean 验证程序实现,则如何注入专用的验证程序工厂实例和验证程序实例。 例如,在上述情况下,它是 Hiberate 验证器。
  • 如何使用注解配置来验证 Java Bean。

学习愉快!

[已解决] UnexpectedTypeException - 找不到约束验证器

原文: https://howtodoinjava.com/hibernate/unexpectedtypeexception-error/

在使用 Hiberate 验证器进行 Java Bean 验证时,学习解决 Java 异常UnexpectedTypeException

1)问题

当您尝试在任何 bean 属性上使用错误的 Hiberate 验证器注解时,都会出现UnexpectedTypeException错误。 错误栈跟踪看起来像这样。

Exception in thread "main" javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'javax.validation.constraints.NotBlank' validating type 'java.lang.Long'. Check configuration for 'id'
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getExceptionForNullValidator(ConstraintTree.java:108)
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getInitializedConstraintValidator(ConstraintTree.java:140)
	at org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree.validateConstraints(SimpleConstraintTree.java:55)
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:73)
	at org.hibernate.validator.internal.metadata.core.MetaConstraint.doValidateConstraint(MetaConstraint.java:127)
	at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:120)
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:533)
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForSingleDefaultGroupElement(ValidatorImpl.java:496)
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:465)
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:430)
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:380)
	at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:169)
	at com.howtodoinjava.example.TestHibernateValidator.main(TestHibernateValidator.java:25)

例如,您收到上述错误,您尝试将@NotBlank注解放在任何非字符串类型的字段中,例如java.lang.Long类型。

public class User {

    @NotBlank			//Incorrect usage
    private Long id;

    //More fields
 }

阅读更多:Hiberate 验证器

2)解决方案

如问题所述,要解决此错误,您必须使用正确的注解。 在上述问题中,@NotBlank注解只能应用于任何String字段。

要验证long类型字段,请使用注解@NotNull

public class User {

public class User {

    @NotNull(message = "Please enter id")
    private Long id;

    @Size(max = 20, min = 3, message = "{user.name.invalid}")
    @NotEmpty(message = "Please enter name")
    private String name;

    //More fields
 }

将我的问题放在评论部分。

学习愉快!

在 Eclipse 中逐步创建 Maven Web 项目

原文: https://howtodoinjava.com/maven/maven-web-project-in-eclipse/

了解在 Eclipse 中创建 Maven Web 项目,我们应该能够在 Eclipse IDE 上将其导入以进行进一步开发。

要创建支持 Eclipse 的 Web 项目,我们需要首先创建一个普通的 Maven 应用,然后使其与 Eclipse IDE 兼容。

1. 在 Eclipse 中创建 Maven Web 项目

运行此 maven 命令以创建名为“demoWebApplication”的 maven Web 项目使用的 Maven 原型是“maven-archetype-webapp”。

$ mvn archetype:generate 
			-DgroupId=com.howtodoinjava 
			-DartifactId=demoWebApplication
			-DarchetypeArtifactId=maven-archetype-webapp 
			-DinteractiveMode=false

这将创建 maven Web 项目结构和特定于 Web 应用的文件,例如web.xml

create web project using maven

2. 转换为 Eclipse 动态 Web 项目

要将创建的 maven Web 项目转换为 Eclipse 动态 Web 项目,需要运行以下 maven 命令。

$ mvn eclipse:eclipse -Dwtpversion=2.0

convert to eclipse webproject

请记住,必须添加“-Dwtpversion=2.0”,否则仅使用“mvn eclipse:eclipse”会将其转换为仅普通的 Java 项目(无 Web 支持),并且您将无法将其作为 Web 应用运行。

3. 在 Eclipse 中导入 Web 项目

  1. 单击文件菜单,然后单击导入选项。

    import project menu

  2. 现在,在常规部分中单击“现有项目..”。

    Existing project menu

  3. 现在,浏览项目的根文件夹,然后单击“确定”。 完成。

    Browse project menu

  4. 上面的步骤会将项目导入 Eclipse 工作区。 您可以像这样验证项目结构。

    project created success

在本 Maven 教程中,我们学习了如何在 Eclipse 中创建 Maven 动态 Web 项目。 在此示例中,我使用了 Oxygen。 您可能具有不同的 Eclipse 版本,但遵循的步骤将相同。

学习愉快!

Hiberate 注解

Hibernate / JPA2 持久化注解教程

原文: https://howtodoinjava.com/hibernate/hibernate-jpa-2-persistence-annotations-tutorial/

Hibernate(或 JPA2)持久化注解教程包含对 Java POJO 进行注解以使其充当持久化 JPA 实体时可能需要的所有重要注解的概述。 本教程首先定义一个 POJO “EmployeeEntity”,在其中定义一些属性,并具有各自的获取器和设置器方法。 在学习新注解时,我们将这些注解应用于EmployeeEntity,然后我们将了解该特定注解的含义。

让我们快速列出注解,我们将在本教程中进行讨论。

Table of Contents

**Most used JPA Annotations**
	Entity Beans with `@Entity`
	Primary Keys with `@Id` and `@GeneratedValue`
	Generating Primary Key Values with `@SequenceGenerator`
	Generating Primary Key Values with `@TableGenerator`
	Compound Primary Keys with `@Id`, `@IdClass`, or `@EmbeddedId`
	Database Table Mapping with `@Table` and `@SecondaryTable`
	Persisting Basic Types with `@Basic`
	Omitting Persistence with `@Transient`
	Mapping Properties and Fields with `@Column`
**Modeling Entity Relationships**
**Mapping Inheritance Hierarchies**
	Single Table
	Joined Table
	Table per Concrete Class
**Other JPA2 Persistence Annotations**
	Temporal Data with `@Temporal`
	Element Collections with `@ElementCollection`
	Large Objects with `@Lob`
	Mapped Superclasses with `@MappedSuperclass`
	Ordering Collections with `@OrderColumn`
**Named Queries (HQL or JPQL)**
	`@NamedQuery` and `@NamedQueries`
	Named Native Queries using `@NamedNativeQuery`

上面的列表是一个很大的列表,列出的项目的完整详细信息的范围较小,但是我将尝试包括所有相关信息,这将有助于您做出决定。 首先,我们写下EmployeeEntity POJO,然后在整个讨论中开始使用JPA annotations装饰它。

package com.howtodoinjava.demo.test;

import java.io.Serializable;

public class EmployeeEntity implements Serializable
{
   private static final long serialVersionUID = 570235488278506106L;

   private Integer           employeeId;
   private String            firstName;
   private String            lastName;

   public Integer getEmployeeId()
   {
      return employeeId;
   }

   public void setEmployeeId(Integer employeeId)
   {
      this.employeeId = employeeId;
   }

   public String getFirstName()
   {
      return firstName;
   }

   public void setFirstName(String firstName)
   {
      this.firstName = firstName;
   }

   public String getLastName()
   {
      return lastName;
   }

   public void setLastName(String lastName)
   {
      this.lastName = lastName;
   }
}

最常用的 JPA 注解

Hibernate / JPA2 @Entity注解教程

这是将 POJO 标记为 JPA 实体的第一步。 为此,我们需要应用@Entity注解,如下所示:

import javax.persistence.Entity;

@Entity
public class EmployeeEntity implements Serializable
{
	public EmployeeEntity(){

	}
	//Other code
}

JPA2 标准注解包含在javax.persistence包中,因此我们从此包中导入了注解。 @Entity注解将该类标记为实体 Bean,因此它必须具有无参数构造器,该构造器至少在受保护的范围(特定于 JPA)下可见。 Hibernate 至少支持包作用域,但是您会失去对其他 JPA 实现的可移植性,因为它们可能只允许受保护级别作用域。 理想情况下,您应该将此构造器公开,这也使其与其他规范高度兼容。 还有更多规则,例如 POJO 类的欲望不是最终的。 而且也不能是抽象的。

现在,让我们快速浏览一下“@org.hibernate.annotations.Entity”注解(特定于 Hiberate)。 使用它,您可以为特定于 Hiberate 的实体定义一些额外的行为。

@javax.persistence.Entity仍然是强制性的,@org.hibernate.annotations.Entity是一种鼓励,而不是替代。

@javax.persistence.Entity
@org.hibernate.annotations.Entity(
      selectBeforeUpdate = true,
      dynamicInsert = true, dynamicUpdate = true,
      optimisticLock = OptimisticLockType.ALL,
      polymorphism = PolymorphismType.EXPLICIT,
      mutable = false)
public class EmployeeEntity implements Serializable
{
	public EmployeeEntity(){

	}
	//Other code
}

所有上述属性在很久以前就已被标记为已弃用,但仍受支持。 我仅给出示例供参考。

实际上,在新版本的 Hiberate 下,您根本不需要使用@org.hibernate.annotations.Entity。 相反,您可以仅使用注解直接添加所需的行为。 让我们看一下上面编写的实体的等效代码。

import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.OptimisticLockType;
import org.hibernate.annotations.OptimisticLocking;
import org.hibernate.annotations.Polymorphism;
import org.hibernate.annotations.PolymorphismType;
import org.hibernate.annotations.SelectBeforeUpdate;

@javax.persistence.Entity
@SelectBeforeUpdate
@DynamicInsert
@DynamicUpdate
@Polymorphism (type = PolymorphismType.EXPLICIT)
@OptimisticLocking (type = OptimisticLockType.ALL)
public class EmployeeEntity implements Serializable
{
	public EmployeeEntity(){

	}
	//Other code
}

继续,如果需要,在代码中使用这些注解。

使用@Id@GeneratedValue的主键

每个实体 bean 必须具有一个主键,您可以在主类上使用@Id注解对其进行注解。 通常,主键将是单个字段,尽管它也可以是多个字段的组合,我们将在后面的部分中看到。

@Id注解的位置决定了 Hibernate 将用于映射的默认访问策略。 如果将注解应用于字段,如下所示,则将使用“字段访问”。

@Id
private Integer employeeId;

相反,如果将注解应用于字段的访问器,则将使用属性访问。

@Id
public Integer getEmployeeId()
{
  return employeeId;
}

属性访问意味着 Hibernate 将调用获取器和设置器,而不是直接直接设置字段,而在字段访问的情况下,它会这样做。 如果需要,这可以灵活地更改在id字段中设置的实际值的值。 此外,您还可以在设置器中为其他字段设置“id”字段时应用额外的逻辑。

默认情况下,@Id注解不会创建主键生成策略,这意味着作为代码的作者,您需要通过设置显式调用设置器方法的方式来确定有效的主键。 或者,您可以使用@GeneratedValue注解。

@GeneratedValue注解具有一对属性:strategygenerator 如下:

@Id
@GeneratedValue (strategy = GenerationType.SEQUENCE)
private Integer employeeId;

//OR a more complex use can be

@Id
@GeneratedValue(strategy=GenerationType.TABLE , generator="employee_generator")
@TableGenerator(name="employee_generator",
			   table="pk_table",
			   pkColumnName="name",
			   valueColumnName="value",
			   allocationSize=100)
private Integer employeeId;

策略属性必须是javax.persistence.GeneratorType枚举中的值。 如果未指定生成器类型,则默认为AUTOGeneratorType上有四种不同类型的主键生成器,如下所示:

  1. AUTO:Hibernate 根据数据库对主键生成的支持来决定使用哪种生成器类型。
  2. IDENTITY:数据库负责确定和分配下一个主键。
  3. SEQUENCE:某些数据库支持SEQUENCE列类型。 它使用@SequenceGenerator
  4. TABLE:此类型保留一个带有主键值的单独表。 它使用@TableGenerator

generator属性允许使用上面的代码示例中所示的自定义生成机制。

使用@SequenceGenerator生成主键值

序列是一个数据库对象,可用作主键值的源。 它与标识列类型的使用类似,不同之处在于序列独立于任何特定表,因此可以被多个表使用。

要声明要使用的特定序列对象及其属性,必须在带注解的字段上包含@SequenceGenerator注解。 这是一个例子:

@Id
@SequenceGenerator(name="seq1",sequenceName="HIB_SEQ")
@GeneratedValue(strategy=SEQUENCE,generator="seq1")
private Integer employeeId;

在此,已声明了名为seq1的序列生成注解。 这指的是称为HIB_SEQ的数据库序列对象。 然后,将名称seq1引用为@GeneratedValue注解的生成器属性。 仅序列生成器名称是必需的; 其他属性将采用合理的默认值,但是作为一种好的做法,您应该为sequenceName属性提供一个明确的值。 如果未指定,则持久化供应器将选择要使用的sequenceName值。

使用@TableGenerator生成主键值

@TableGenerator注解的使用方式与@SequenceGenerator注解非常相似,但是由于@TableGenerator操纵标准数据库表来获取其主键值,因此无需使用特定于供应商的序列对象,因此可以保证 在数据库平台之间可移植。

为了获得最佳的可移植性和最佳性能,您不应指定使用表生成器,而应使用@GeneratorValue(strategy=GeneratorType.AUTO)配置,该配置允许持久性提供程序为数据库选择最合适的策略。

与序列生成器一样,@TableGenerator的名称属性是必需的,其他属性是可选的,并且表详细信息由持久化供应器选择。 让我们再来看一个例子。

@Id
@GeneratedValue(strategy=GenerationType.TABLE , generator="employee_generator")
@TableGenerator(name="employee_generator",
			   table="pk_table",
			   pkColumnName="name",
			   valueColumnName="value",
			   allocationSize=100)
private Integer employeeId;

可选属性如下:

  • allocationSize:允许一次设置主键的数量以提高性能。
  • catalog:允许指定表所在的目录。
  • initialValue:允许指定起始主键值。
  • pkColumnName:允许标识表的主键列。 该表可以包含为多个实体生成主键值所需的详细信息。
  • pkColumnValue:允许标识包含主键生成信息的行的主键。
  • schema:允许指定表所在的模式。
  • table:包含主键值的表的名称。
  • uniqueConstraints:允许将其他约束应用于表以生成模式。
  • valueColumnName:允许标识包含当前实体的主键生成信息的列。

因为该表可用于包含各种条目的主键值,所以使用该表的每个实体可能只有一行。 因此,它需要自己的主键(pkColumnName),以及包含从中获取主键的任何实体要使用的下一个主键值(pkColumnValue)的列。

使用@Id@IdClass@EmbeddedId的复合主键

尽管出于各种原因使用单列代理键是有利的,但有时您可能不得不使用业务键。 当它们包含在单个列中时,您可以使用@Id而无需指定生成策略,该策略强制用户在持久保存实体之前分配主键值。

但是,如果是多列主键,则必须创建一个代表该主键的类。 当然,它不需要自己的主键,但它必须是公共类,必须具有默认构造器,必须可序列化,并且必须实现hashCode()equals()方法,以允许 Hibernate 代码测试主键冲突。

一旦创建了此主键类,就可以使用以下三种策略:

  1. 将其标记为@Embeddable并为其添加一个普通属性,并标记为@Id
  2. 向您的实体类添加一个普通属性,标记为@EmbeddableId
  3. 将属性的所有字段添加到您的实体类中,用@Id标记它们,并用@IdClass标记您的实体类,以提供主键类的类。

使用带有标记为@Embeddable的类的@Id是最自然的方法。 无论如何,@Embeddable标签可用于非主键可嵌入值。 它允许您将复合主键视为单个属性,并允许在其他表中重用@Embeddable类。

值得一提的是:嵌入式主键类必须可序列化。

可以在这里阅读详细的示例: http://docs.oracle.com/javaee/6/api/javax/persistence/Embeddable.html

使用@Table@SecondaryTable进行数据库表映射

默认情况下,表名称是从实体名称派生的。 因此,给定一个带有简单@Entity注解的Employee类,表名将为“employee”,并根据数据库的配置进行了调整。 如果实体名称发生更改(通过在@Entity注解中提供其他名称,例如@Entity("EMP_MASTER"),则新名称将用作表名。

可以进一步自定义表名,并且可以通过@Table注解配置其他与数据库相关的属性。 该注解允许您指定表的许多详细信息,这些详细信息将用于将实体保留在数据库中。 如前所述,如果省略注解,则 Hibernate 将默认使用类名作为表名,因此,如果要覆盖该行为,则只需提供此注解。 @Table注解提供了四个属性,使您可以覆盖表的名称,表的目录及其架构,并对表中的列实现唯一约束。 通常,您只能提供一个替代表名称,例如:@Table(name="ORDER_HISTORY")。 如果数据库模式是从带注解的类生成的,则将应用唯一约束,并将补充任何特定于列的约束。 否则不会强制执行。

@SecondaryTable注解提供了一种对实体 bean 进行建模的方法,该实体 bean 跨多个不同的数据库表保留。 在这里,除了为主数据库表提供@Table注解之外,您的实体 bean 还可以具有@SecondaryTable注解或包含零个或多个@SecondaryTable注解的@SecondaryTables注解。 @SecondaryTable注解具有与@Table注解相同的基本属性,但附加了join属性。 join属性定义主数据库表的连接列。 它接受javax.persistence.PrimaryKeyJoinColumn对象的数组。 如果省略join属性,则将假定表在相同名称的主键列上进行了连接。

从辅助表中提取实体中的属性时,必须使用@Column注解对其进行标记,并使用表属性标识适当的表。

@Entity
@Table(name = "employee")
@SecondaryTable(name = "employee_details")
public class EmployeeEntity implements Serializable
{
   @Id
   @GeneratedValue (strategy = GenerationType.SEQUENCE)
   private Integer employeeId;
   private String  firstName;
   private String  lastName;

   @Column(table = "employee_details")
   public String address;
}

通过在@Table@SecondaryTableuniqueConstraints属性中添加一个或多个适当的@UniqueConstraint注解,可以将主表或辅助表中的列标记为具有唯一值。 或者,您也可以在@Column属性上的unique属性在字段级别设置唯一性。

@Entity
@Table(
      name="employee",
      uniqueConstraints={@UniqueConstraint(columnNames="firstName")}
      )
@SecondaryTable(name = "employee_details")
public class EmployeeEntity implements Serializable{

}

使用@Basic保留基本类型

默认情况下,POJO 中的属性和实例变量是持久化的。 Hibernate 将为您存储它们的值。 因此,最简单的映射适用于“基本”类型。 这些包括基本类型,基本类型包装器,基本类型或包装器数组,枚举以及实现Serializable但本身不是映射实体的任何类型。

这些都隐式映射 - 无需注解。 默认情况下,此类字段被映射到单个列,并且热切获取用于检索它们(即,当从数据库中检索实体时,将检索所有基本字段和属性)。 同样,当字段或属性不是基元时,可以将其存储和检索为空值。

通过将@Basic注解应用于适当的类成员,可以覆盖此默认行为。 注解具有两个可选属性,并且本身是完全可选的。 第一个属性被命名为可选,并带有一个布尔值。 默认为true,可以将其设置为false,以提示架构生成应创建关联列NOT NULL。 第二个名为fetch,它采用枚举FetchType的成员。 默认情况下为EAGER,但可以设置为LAZY以允许在访问值时加载。

@Basic (fetch = FetchType.LAZY, optional = false)
private String  firstName;

延迟加载的使用不太可能有价值,除非将大型可序列化对象映射为基本类型(而不是为其提供给定的实体映射),并且检索时间可能变得很长。 虽然必须遵守(默认)EAGER值,但是LAZY标志被视为提示,并且可以由持久化引擎忽略。

通常会省略@Basic属性,而使用@Column属性,否则可能会使用@Basic注解的可选属性来提供NOT NULL行为。

使用@Transient省略持久化

某些字段(例如,计算值)只能在运行时使用,并且应将它们保留在数据库中,并从对象中将其丢弃。 JPA 规范为这些瞬态字段提供了@Transient注解。 @Transient注解没有任何属性 - 您只需将其添加到实例变量或适合实体 bean 的属性访问策略的设置器方法中即可。

@Transient注解突出显示了在 Hibernate 中使用注解和使用 XML 映射文档之间的重要区别之一。 使用注解,Hibernate 将默认保留所有字段在映射对象上的持久化。 使用 XML 映射文档时,Hibernate 要求您明确告诉它哪些字段将被保留。

例如,如果我们的EmployeeEntity有两个附加字段“age”和“dateOfBirth”,则您想将dateOfBirth存储在数据库中,但是您想根据dateOfBirth的值来计算运行时的年龄。 因此,“age”字段必须标记为瞬态。

@Transient
private Integer age;

使用@Column映射属性和字段

@Column注解用于指定字段或属性将映射到的列的详细信息。 其中一些细节与架构相关,因此仅在从注解文件生成架构时才适用。 其他应用则由 Hibernate(或 JPA2 持久化引擎)在运行时应用并强制执行。 它是可选的,具有一组适当的默认行为,但是在覆盖默认行为或需要将对象模型适合到预先存在的架构中时通常很有用。

以下属性通常被覆盖:

  1. name:允许明确指定列的名称 - 默认情况下,这将是属性的名称。
  2. length :允许显式定义用于映射值(尤其是String值)的列的大小。 列大小默认为 255,例如,否则可能会导致字符串数据被截断。
  3. nullable:允许在生成架构时将该列标记为NOT NULL。 默认设置是字段应允许为空; 但是,当字段是或应该是必填字段时,通常会覆盖此字段。
  4. unique:允许将该列标记为仅包含唯一值。 默认为false,但通常将其设置为一个值,该值可能不是主键,但是如果重复(例如用户名)仍会引起问题。
@Column(name="FNAME",length=100,nullable=false)
private String  firstName;

还有更多属性,这些属性在现实生活项目中很少使用。 这些是table, insertable, updatable, columnDefinition, precisionscale。 我将让您详细探讨它们。

建模实体关系

我已经在单独的详细帖子中介绍了与建模相关的概念。 请在这些链接的文章中阅读有关它们的更多信息,因为此处没有重复的信息是没有意义的。

  1. 建模@OneToOne关系
  2. 建模@OneToMany关系
  3. 建模@ManyToMany关系

映射继承层次结构

实体并不总是与其他实体关联为属性; 有时,它们使用常规的 OOP 继承规则进行关联。 Hibernate 允许您使用@Inheritance注解来履行此类关系。

JPA2 标准和 Hibernate 都支持将继承层次结构映射到数据库中的三种方法。 这些如下:

  1. 单个表(SINGLE_TABLE:每个类层次结构都有一个表
  2. 已连接(JOINED:每个子类一个表(包括接口和抽象类)
  3. 每类表(TABLE_PER_CLASS:每个具体类实现一个表

与继承相关的持久化实体必须使用@Inheritance注解进行标记。 这采用单个策略属性,该属性设置为与这些方法相对应的三个javax.persistence.InheritanceType枚举值之一(即SINGLE_TABLEJOINEDTABLE_PER_CLASS)。

让我们详细讨论一下。

单表

单表方法为主要超类及其所有子类型管理一个数据库表。 超类的每个映射字段或属性以及派生类型的每个不同字段或属性都有列。 遵循此策略时,您将需要确保在层次结构中任何字段或属性名称冲突时,对列进行适当的重命名。

为了确定从数据库中检索实体时要实例化的适当类型,应在持久化层次结构的根(且仅在根中)中提供@DiscriminatorColumn注解。

现在让我们看一个简单的例子。 我要让您阅读更多有关 Hiberate 的官方文档中的内容。 我将在以后的文章中详细介绍它们。

//The Root of the Inheritance Hierarchy Mapped with the SINGLE_TABLE Strategy

@Entity
@Inheritance(strategy = SINGLE_TABLE)
@DiscriminatorColumn(
    name="DISCRIMINATOR",
    discriminatorType=INTEGER
)
@DiscriminatorValue("1")
public class Book {
...
}

//A Derived Entity in the Inheritance Hierarchy
@Entity
@DiscriminatorValue("2")
public class ComputerBook extends Book {
...
}

连接表

整体式单表方法的替代方法是其他类似的联合表方法。 此处使用了“区分符”列,但各种派生类型的字段存储在不同的表中。

@Entity
@Inheritance(strategy = JOINED)
@DiscriminatorColumn
    name="DISCRIMINATOR"
)
public class Book {
...
}

每类表

最后,有一种每类表方法,其中继承层次结构中每种类型的所有字段都存储在不同的表中。 由于实体与其表之间的紧密对应关系,因此@DiscriminatorColumn注解不适用于此继承策略。

@Entity
@Inheritance(strategy = TABLE_PER_CLASS)
public class Book {
...
}

其他 JPA2 持久化注解

尽管我们现在涵盖了大多数核心 JPA2 持久化注解,但是您还会经常遇到其他一些注解。 我们将在以下各节中介绍其中一些内容。

@Temporal的时间数据

具有java.util.Datejava.util.Calendar类型的实体的字段或属性表示时间数据。 默认情况下,它们将存储在TIMESTAMP数据类型的列中,但是可以用@Temporal注解覆盖此默认行为。

注解接受javax.persistence.TemporalType枚举的单个值属性。 这提供了三个可能的值:DATETIMETIMESTAMP。 这些分别对应于java.sql.Datejava.sql.Timejava.sql.Timestamp。 在架构生成时为table列提供了适当的数据类型。

@Temporal(TemporalType.TIME)
java.util.Date startingTime;

@ElementCollection的元素集合

除了使用一对多映射来映射集合之外,JPA2 还引入了@ElementCollection注解,用于映射基本或可嵌入类的集合。 您可以使用@ElementCollection注解来简化映射。

@ElementCollection
List<String> passwordHints;

@ElementCollection注解上有两个属性targetClassfetchtargetClass属性告诉 Hibernate 集合中存储了哪个类。 如果在集合上使用泛型,则无需指定targetClass,因为 Hibernate 会推断出正确的类。 fetch属性采用枚举FetchType的成员。 默认情况下为EAGER,但可以将其设置为LAZY以在访问该值时允许加载。

@Lob的大对象

通过应用@Lob注解,可以将持久化属性或字段标记为持久化数据库支持的大对象类型。

注解不带任何属性,但是将从字段或参数的类型中推断出要使用的基础大对象类型。 基于字符串和字符的类型将存储在适当的基于字符的类型中,即 CLOB。 所有其他对象将存储在 BLOB 中。

@Lob
String content; // a very long article

@Lob注解可以与@Basic@ElementCollection注解结合使用。

@MappedSuperclass映射超类

当层次结构的根本身不是持久实体,而是派生自它的各种类时,就会发生继承的特殊情况。 这样的类可以是抽象的或具体的。 @MappedSuperclass注解允许您利用这种情况。

标有@MappedSuperclass的类不是实体,并且不可查询(不能传递给在SessionEntityManager对象中需要实体的方法)。 它不能是关联的目标。

超类列的映射信息将与派生类的详细信息存储在同一表中。

使用@OrderColumn排序集合

尽管@OrderBy允许从数据库中检索数据后进行排序,但 JPA2 还提供了一个注解,该注解允许在数据库中维护适当集合类型(例如List)的排序; 它通过维护代表该顺序的有序列来实现。 这是一个例子:

@OneToMany
@OrderColumn(
   name="employeeNumber"
)
List<Employee> employees;

在这里,我们声明一个employeeNumber列将保持一个值,从 0 开始,并随着每个条目添加到列表中而递增。 默认的起始值可以被 base 属性覆盖。 默认情况下,该列可以包含空(无序)值。 通过将nullable属性设置为false可以覆盖可空性。 默认情况下,从注解生成模式时,该列被假定为整数类型; 但是,可以通过提供指定不同列定义字符串的columnDefinition属性来覆盖此属性。

命名查询(HQL 或 JPQL)

@NamedQuery@NamedQueries

@NamedQuery@NamedQueries允许将一个或多个 Hiberate 查询语言或 Java 持久化查询语言(JPQL)查询与实体相关联。 必需的属性如下:

  1. name是用于检索查询的名称。
  2. query是与名称关联的 JPQL(或 HQL)查询。

以下面的“Author”实体为例。

@Entity
@NamedQuery(
        name="findAuthorsByName",
        query="from Author where name = :author"
)
public class Author {
...
}

该查询将按名称检索Author实体,因此将其与该实体相关联是很自然的。 但是,并没有实际要求以这种方式将命名查询与其所涉及的实体相关联。

您不需要直接将查询与其声明所针对的实体相关联,但是通常这样做。 如果查询与任何实体声明没有自然关联,则可以在包级别进行@NamedQuery注解。

使用@NamedNativeQuery@NamedNativeQueries命名本地查询

@NamedNativeQuery允许您编写命名的 SQL 查询,而@NamedQuery允许您编写命名的 HQL 查询(或 JPQL)。

通常,您应该更喜欢编写 HQL 查询,因为这样您就可以让 Hibernate 处理将 HQL 转换为各种 SQL 方言的复杂过程。 当您选择切换 DBMS 供应器时,这将使您的工作简单得多。

@NamedQueries({
   @NamedQuery(name="get-emp-by-name",query="FROM EmployeeBean WHERE fName=:fName")
})

//Equivalent NamedNativeQuery

@NamedNativeQueries(
	{
		@NamedNativeQuery(
			name="get-emp-by-name-native",
			query="SELECT * FROM Employees WHERE firstName=:fName",
			resultClass=EmployeeEntity.class)
	}
)

简而言之,这就是本篇有限的教程,涵盖了最重要的 JPA2 持久化注解。 我将在以后的教程中详细介绍它们。

祝您学习愉快!

Hiberate 注解与映射 – 优缺点

原文: https://howtodoinjava.com/hibernate/pros-and-cons-of-hibernate-annotations-vs-mappings/

您可能知道,在内联注解之前,创建 Hiberate 映射的唯一方法是通过 XML 文件。 尽管来自 Hibernate 和第三方项目的各种工具允许部分或全部映射从 Java 源代码自动生成。 如今,注解是定义映射的最新方法,但并不是自动的最佳方法。 让我们先讨论 Hiberate(或我应该说 JPA)注解的缺点和好处,然后再讨论何时应用它们。

Hiberate 注解的缺点

让我们一一列出所有可能的缺点。

  • 如果要从 Hibernate 2 环境升级或使用现有的 Hibernate 3 环境,则您将已经具有基于 XML 的映射文件来支持您的代码库。 在所有其他条件都相同的情况下,您不希望仅出于注解目的而使用注解重新表达这些映射。 您将要坚持使用映射,因为它们仍然可以正常运行并且运作良好。
  • 因此,如果您要从旧版环境迁移,则可能不希望更改现有的 POJO 源代码,换句话说,您将不会注入可能存在错误的已知良好代码。
  • 如果您没有 POJO 的源代码(因为它是由自动化工具或类似的代码(例如旧版代码)生成的),则与反编译类文件以获得 Java 源代码相比,您可能更喜欢使用基于 XML 的外部映射 更改代码。
  • 通过将映射信息保留为外部 XML 文件,可以修改映射信息以反映业务更改或架构更改,而不必强制您重新构建整个应用。

Hiberate 注解的优点

考虑了缺点之后,使用注解会有一些强大的好处。

  • 首先,也许是最有说服力的是,我们发现基于注解的映射比基于 XML 的替代更加直观,因为它们与相关的属性一起直接出现在源代码中。 大多数编码人员倾向于使用注解,因为必须保持较少的文件同步。

  • 部分原因是,注解不如 XML 等效项那么冗长。 让我们看一下比较

    import javax.persistence.* ;
    @Entity
    public class Sample {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Integer id;
        public String name;
    }
    
    

    并将其与等效的映射文件进行比较。

    <?xml version='1.0' encoding='utf-8'?>
    <!DOCTYPE
       hibernate-mapping
       PUBLIC
       "-//Hibernate/Hibernate Mapping DTD//EN"
       "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd
    ">
    <hibernate-mapping default-access="field">
       <class name="Sample">
          <id type="int" column="id">
             <generator class="native"/>
          </id>
          <property name="name" type="string"/>
       </class>
    </hibernate-mapping>
    
    

    后者的一些冗长是 XML 本身的本质(标记名和样板文档类型声明),而某些是由于注解与源代码的紧密集成所致。

  • 另一个主要的好处是 Hiberate 使用并支持 JPA2 持久化注解。 如果选择不在代码和注解中使用特定于 Hibernate 的功能,则可以使用支持 JPA2 的其他 ORM 工具将实体部署到环境中。

  • 最后,也许是次要的一点,因为注解被直接编译到适当的类文件中,因此丢失或陈旧的映射文件在部署时引起问题的风险较小。

选择使用哪个

通常,更喜欢注解; 注解本身可以在 JPA 实现中移植,并且众所周知。 工具可以直接从数据库中创建带注解的源代码,因此,即使使用预先存在的模式,同步也没有多少麻烦。
XML 映射既可以采用 Hibernate 的专有格式,也可以采用 JPA 的标准 XML 配置,它们相似但不相同; 如果您以某种方式发现 XML 是首选的配置格式,则最好使用行业标准 JPA 配置中的 XML 格式。

让我知道您的想法,您更喜欢哪种,为什么?

祝您学习愉快!

@Immutable@NaturalId – 特定于 Hiberate 的注解

原文: https://howtodoinjava.com/hibernate/immutable-and-naturalid-hibernate-specific-annotations/

在上一篇文章中,我们了解了Hiberate中最常用的 JPA 注解,还了解了与这些 JPA 注解互补的 Hiberate 注解。 使用 JPA 注解使您的应用代码可移植到其他 JPA 实现中,这是一件好事。 除了 JPA 注解之外,Hiberate 还具有一些自己的注解,您可以使用它们在应用代码中具有某些功能。 但是您必须记住,将来的日期可能很难使您的代码可移植。

阅读更多: JPA2 持久化注解教程

在本文中,我们将学习两个特定于 Hibernate 的此类注解。

1)@Immutable注解

@Immutable注解将一个实体标记为不可变。 这在您的实体表示参考数据的情况下非常有用,例如状态列表,性别或其他很少突变的数据。

由于状态之类的东西很少会更改,因此通常有人会通过 SQL 或管理应用手动更新数据。 Hibernate 可以积极地缓存此数据,需要将其考虑在内。 如果参考数据发生变化,则需要确保已通知使用该数据的应用(可以使用refresh()方法)或以某种方式重新启动。

@Immutable注解告诉 Hibernate,对不可变实体的任何更新都不应传递给数据库而不会给出任何错误。 @Immutable也可以放在集合上; 在这种情况下,对集合的更改(添加或删除)将引发HibernateException

EmployeeEntity.java

import org.hibernate.annotations.Immutable;

@Immutable
@Entity 
@Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
	private static final long serialVersionUID = -1798070786993154676L;

	@Id
	@Column(name = "ID", unique = true, nullable = false)
	private Integer           employeeId;
	@Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
	private String            firstName;
	@Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
	private String            lastName;

	//Setters and Getters
}

ImmutableAnnotationExample.java

public class ImmutableAnnotationExample
{
   public static void main(String[] args)
   {
      setupTestData();

      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      //Load the employee in another session
      EmployeeEntity employee = (EmployeeEntity) sessionOne.load(EmployeeEntity.class, 1);

      //Update the first name
      employee.setFirstName("Alex");
      sessionOne.flush();
      sessionOne.close();

      Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
      sessionTwo.beginTransaction();

      //Load the employee in another session
      EmployeeEntity employeeUpdated = (EmployeeEntity) sessionTwo.load(EmployeeEntity.class, 1);

      //Verify the first name
      System.out.println(employeeUpdated.getFirstName());

      sessionTwo.flush();
      sessionTwo.close();
      HibernateUtil.shutdown();
   }  

   private static void setupTestData(){
      Session session = HibernateUtil.getSessionFactory().openSession();
      session.beginTransaction();

      //Create Employee
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");
      session.save(emp);

      session.getTransaction().commit();
      session.close();
   }
}

Output:

Hibernate: insert into Employee (FIRST_NAME, LAST_NAME, ID) values (?, ?, ?)

Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_, employeeen0_.LAST_NAME as LAST_NAM3_1_0_ 
from Employee employeeen0_ where employeeen0_.ID=?

Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_, employeeen0_.LAST_NAME as LAST_NAM3_1_0_ 
from Employee employeeen0_ where employeeen0_.ID=?

Lokesh //Value didn't updated in database i.e. immutable

2)@NaturalId注解

在过去的教程中,我们了解了很多有关@Id@GeneratedValue注解以为数据库中的记录创建主键的知识。 在大多数实际应用中,这些主键是“人工主键”,并且仅在应用运行时内部引用。 但是,还存在“自然 ID”的概念,除了人工或复合主键之外,它还提供了另一种方便且合乎逻辑的方式来引用实体。

自然 ID 的示例可能是美国的社会安全号码或税号,而印度则是 PAN 号码。 实体(是个人或公司)可能具有由 Hibernate 生成的人为主键,但也可能具有唯一的税标识符。 Hibernate 还允许您基于这些自然 ID 搜索和加载实体。

对于自然 ID,有两种形式的加载机制: 一种使用简单自然 ID(其中自然 ID 是一个且仅一个字段),另一种使用命名属性作为复合自然 ID 的一部分。

简单的自然 ID 示例

@Entity 
@Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
   private static final long serialVersionUID = -1798070786993154676L;
   @Id
   @Column(name = "ID", unique = true, nullable = false)
   private Integer           employeeId;
   @Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
   private String            firstName;
   @Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
   private String            lastName;

   //Natural id can be SSN as well
   @NaturalId
   Integer SSN;

   //Setters and Getters
}

SimpleNaturalIdExample.java

public class SimpleNaturalIdExample
{
   public static void main(String[] args)
   {
      setupTestData();

      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      //Load the employee
      EmployeeEntity employee1 = (EmployeeEntity) sessionOne.load(EmployeeEntity.class, 1);
      //Just to ensure that employee is loasded from DB
      System.out.println(employee1.getFirstName());

      //Get the employee for natural id i.e. SSN; This does not execute another SQL SELECT as entity is already present in session
      EmployeeEntity employee2 = (EmployeeEntity) sessionOne.bySimpleNaturalId(EmployeeEntity.class).load(12345);

      //Verify that employee1 and employee2 refer to same object
      assert(employee1 == employee2);

      sessionOne.flush();
      sessionOne.close();

      System.out.println("====================================");

      Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
      sessionTwo.beginTransaction();

      //Get the employee for natural id i.e. SSN; entity is not present in this session
      EmployeeEntity employee = (EmployeeEntity) sessionTwo.bySimpleNaturalId(EmployeeEntity.class).load(12345);

      sessionTwo.flush();
      sessionTwo.close();
      HibernateUtil.shutdown();
   }  

   private static void setupTestData(){
      Session session = HibernateUtil.getSessionFactory().openSession();
      session.beginTransaction();

      //Create Employee
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");
      emp.setSSN(12345);
      session.save(emp);

      session.getTransaction().commit();
      session.close();
   }
}

Output:

Hibernate: insert into Employee (SSN, FIRST_NAME, LAST_NAME, ID) values (?, ?, ?, ?)

Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.SSN as SSN2_1_0_, employeeen0_.FIRST_NAME as FIRST_NA3_1_0_,
 employeeen0_.LAST_NAME as LAST_NAM4_1_0_ from Employee employeeen0_ where employeeen0_.ID=?

Lokesh
====================================
Hibernate: select employeeen_.ID as ID1_1_ from Employee employeeen_ where employeeen_.SSN=?

Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.SSN as SSN2_1_0_, employeeen0_.FIRST_NAME as FIRST_NA3_1_0_, 
employeeen0_.LAST_NAME as LAST_NAM4_1_0_ from Employee employeeen0_ where employeeen0_.ID=?

请密切注意如果会话中不存在实体,并且如果您使用实体的自然 ID 获取实体,则使用自然 ID 提取第一个主要 ID; 然后使用此主要 ID 提取实体。 如果会话中已经存在实体,则返回相同实体的引用,而无需在数据库中执行其他SELECT语句。

复合自然 ID 示例

@Entity 
@Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
   private static final long serialVersionUID = -1798070786993154676L;
   @Id
   @Column(name = "ID", unique = true, nullable = false)
   private Integer           employeeId;
   @Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
   private String            firstName;
   @Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
   private String            lastName;

   //Natural id part 1
   @NaturalId
   Integer seatNumber;

   //Natural id part 2
   @NaturalId
   String departmentName;

   //Setters and Getters
}

CompositeNaturalIdExample.java

public class CompositeNaturalIdExample
{
   public static void main(String[] args)
   {
      setupTestData();

      Session sessionOne = HibernateUtil.getSessionFactory().openSession();
      sessionOne.beginTransaction();

      //Get the employee for natural id i.e. SSN; entity is not present in this session
      EmployeeEntity employee = (EmployeeEntity) sessionOne.byNaturalId(EmployeeEntity.class)
                                                            .using("seatNumber", 12345)
                                                            .using("departmentName", "IT")
                                                            .load();

      System.out.println(employee.getFirstName());

      sessionOne.flush();
      sessionOne.close();
      HibernateUtil.shutdown();
   }  

   private static void setupTestData(){
      Session session = HibernateUtil.getSessionFactory().openSession();
      session.beginTransaction();

      //Create Employee
      EmployeeEntity emp = new EmployeeEntity();
      emp.setEmployeeId(1);
      emp.setFirstName("Lokesh");
      emp.setLastName("Gupta");
      emp.setSeatNumber(12345);
      emp.setDepartmentName("IT");
      session.save(emp);

      session.getTransaction().commit();
      session.close();
   }
}

Output:

Hibernate: insert into Employee (departmentName, FIRST_NAME, LAST_NAME, seatNumber, ID) values (?, ?, ?, ?, ?)

Hibernate: select employeeen_.ID as ID1_1_ from Employee employeeen_ where employeeen_.departmentName=? and employeeen_.seatNumber=?

Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.departmentName as departme2_1_0_, employeeen0_.FIRST_NAME as FIRST_NA3_1_0_, 
employeeen0_.LAST_NAME as LAST_NAM4_1_0_, employeeen0_.seatNumber as seatNumb5_1_0_ from Employee employeeen0_ where employeeen0_.ID=?

Lokesh

复合自然 ID 的实体获取逻辑与简单自然 ID 相同。 除了使用多个自然键而不是一个以外,没有区别。

这些就是所有这些众所周知的注解。 继续在评论中发表您的想法。

祝您学习愉快!

Hibernate @NaturalId示例教程

原文: https://howtodoinjava.com/hibernate/hibernate-naturalid-example-tutorial/

Hibernate 4 带来了许多改进, @NaturalId就是这样的改进之一。 如您所知,@Id注解用作指定实体主键的元数据。 但是有时,实体通常在 DAO 层代码中使用 ID,该 ID 不是主键,而是其逻辑或自然 ID。 在这种情况下,@NaturalId注解将证明是 Hiberate 下命名查询的良好替代。

例如,在任何应用中都可以有一个雇员实体。 在这种情况下,主键肯定是“员工 ID”,但在通过电子邮件登录等情况下,用户将提供电子邮件和密码。 在这种情况下,您可以直接在“电子邮件”字段上使用@NaturalId注解,而不必编写命名查询。

工作原理

如果您查看日志,那么您会知道,当您通过其自然 ID 获得实体时,

  1. 通过执行自然 ID 的where子句找到实体的第一个主键
  2. 该主键用于获取实体的信息

用法示例

让我们来看这个例子。

作为参考,最新的 Hiberate Maven 依赖项如下:

<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-core</artifactId>
	<version>4.3.0.Beta3</version>
</dependency>

现在,最小员工实体应如下所示:

EmployeeEntity.java

@Entity
@Table(name = "Employee", uniqueConstraints = {
		@UniqueConstraint(columnNames = "ID"),
		@UniqueConstraint(columnNames = "EMAIL") })
public class EmployeeEntity implements Serializable {

	private static final long serialVersionUID = -1798070786993154676L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	private Integer employeeId;

	//Use the natural id annotation here
	@NaturalId (mutable = false)
	@Column(name = "EMAIL", unique = true, nullable = false, length = 100)
	private String email;

	@Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
	private String firstName;

	@Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
	private String lastName;

	//Setters and Getters
}

现在,让我们看看如何在代码中使用它:

TestHibernate.java

public class TestHibernate
{
	public static void main(String[] args) 
	{
		Session session = HibernateUtil.getSessionFactory().openSession();
		session.beginTransaction();

		//Add new Employee object
		EmployeeEntity emp = new EmployeeEntity();
		emp.setEmail("[email protected]");
		emp.setFirstName("demo");
		emp.setLastName("user");
		//Save entity
		session.save(emp);

		EmployeeEntity empGet = (EmployeeEntity) session.bySimpleNaturalId( EmployeeEntity.class ).load( "[email protected]" );

		System.out.println(empGet.getFirstName());
		System.out.println(empGet.getLastName());

		session.getTransaction().commit();
		HibernateUtil.shutdown();
	}
}

Output in console:

Hibernate: insert into Employee (EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?)

Hibernate: select employeeen_.ID as ID1_0_ from Employee employeeen_ where employeeen_.EMAIL=?

Hibernate: select employeeen0_.ID as ID1_0_0_, employeeen0_.EMAIL as EMAIL2_0_0_, employeeen0_.FIRST_NAME as FIRST3_0_0_, employeeen0_.LAST_NAME as LAST4_0_0_ from Employee employeeen0_ where employeeen0_.ID=?

demo
user

要下载以上教程的源代码,请单击下面的下载链接。

祝您学习愉快!

Hiberate 一对多映射注解示例

原文: https://howtodoinjava.com/hibernate/hibernate-one-to-many-mapping-using-annotations/

在两个实体之间进行 Hiberate 的一对多映射,其中第一个实体可以与多个第二个实体实例有关系,而第二个实体只能与第一个实体的一个实例关联。 其 1 与 N 的关系。

例如,在任何公司中,一名员工可以注册多个银行帐户,但是一个银行帐户将与一个且只有一个雇员相关联。 在此 Hiberate 一对多映射注解示例中,我们将学习使用 Hiberate 在数据库中进行此类映射。

Table of Contents

When to use one to many mapping
Hibernate one to many mapping solutions
1\. Hibernate one to many mapping with foreign key association
2\. Hibernate one to many mapping with join table

何时使用一对多映射

使用一个映射来创建实体或对象之间的1..N关系。

例如,我们必须编写两个实体,即EmployeeEntityAccountEntity,以便多个帐户可以与一个雇员相关联,但是一个帐户不能在两个或多个雇员之间共享。

Hiberate 一对多映射解决方案

这个问题可以用两种不同的方式解决。

  1. 一种是在帐户表中具有外键列,即EMPLOYEE_ID。 该列将引用Employee表的主键。 这样,没有两个帐户可以与多个员工关联。 显然,为了执行此限制,帐号必须唯一。
  2. 第二种方法是有一个通用的连接表EMPLOYEE_ACCOUNT。 该表将具有两列,即EMP_ID将是引用EMPLOYEE表中主键的外键,而ACCOUNT_ID将会是引用ACCOUNT表主键的外键。

1. 使用外键关联 Hiberate 一对多映射

在这种方法中,这两个实体将负责建立关系并维护它。 EmployeeEntity应该声明这种关系是一对多的,AccountEntity应该声明它的关系是多对一的。

1.1 设计一对多映射关系

首先让我们看一下架构设计。

one To Many association in hiberate using foreign key

1.2 实体类

编写实体类。

package hibernate.test.oneToMany.foreignKeyAsso;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity(name = "ForeignKeyAssoEntity")
@Table(name = "Employee", uniqueConstraints = {
@UniqueConstraint(columnNames = "ID"),
@UniqueConstraint(columnNames = "EMAIL") })
public class EmployeeEntity implements Serializable {

	private static final long serialVersionUID = -1798070786993154676L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	private Integer employeeId;

	@Column(name = "EMAIL", unique = true, nullable = false, length = 100)
	private String email;

	@Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
	private String firstName;

	@Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
	private String lastName;

	@OneToMany(cascade=CascadeType.ALL)
	@JoinColumn(name="EMPLOYEE_ID")
	private Set<AccountEntity> accounts;

	//Getters and setters
}

编写AccountEntity.java

package hibernate.test.oneToMany.foreignKeyAsso;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity(name = "ForeignKeyAssoAccountEntity")
@Table(name = "ACCOUNT", uniqueConstraints = {
@UniqueConstraint(columnNames = "ID")})
public class AccountEntity implements Serializable
{
	private static final long serialVersionUID = -6790693372846798580L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	private Integer accountId;

	@Column(name = "ACC_NUMBER", unique = true, nullable = false, length = 100)
	private String accountNumber;

	@ManyToOne
	private EmployeeEntity employee;

	//Getters and setters
}

1.3 演示

package hibernate.test.oneToMany;

import hibernate.test.HibernateUtil;
import hibernate.test.oneToMany.foreignKeyAsso.AccountEntity;
import hibernate.test.oneToMany.foreignKeyAsso.EmployeeEntity;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.Session;

public class TestForeignKeyAssociation
{

	public static void main(String[] args)
	{
		Session session = HibernateUtil.getSessionFactory().openSession();
		session.beginTransaction();

		AccountEntity account1 = new AccountEntity();
		account1.setAccountNumber("Account detail 1");

		AccountEntity account2 = new AccountEntity();
		account2.setAccountNumber("Account detail 2");

		AccountEntity account3 = new AccountEntity();
		account3.setAccountNumber("Account detail 3");

		//Add new Employee object
		EmployeeEntity firstEmployee = new EmployeeEntity();
		firstEmployee.setEmail("[email protected]");
		firstEmployee.setFirstName("demo-one");
		firstEmployee.setLastName("user-one");

		EmployeeEntity secondEmployee = new EmployeeEntity();
		secondEmployee.setEmail("[email protected]");
		secondEmployee.setFirstName("demo-two");
		secondEmployee.setLastName("user-two");

		Set<AccountEntity> accountsOfFirstEmployee = new HashSet<AccountEntity>();
		accountsOfFirstEmployee.add(account1);
		accountsOfFirstEmployee.add(account2);

		Set<AccountEntity> accountsOfSecondEmployee = new HashSet<AccountEntity>();
		accountsOfSecondEmployee.add(account3);

		firstEmployee.setAccounts(accountsOfFirstEmployee);
		secondEmployee.setAccounts(accountsOfSecondEmployee);
		//Save Employee
		session.save(firstEmployee);
		session.save(secondEmployee);

		session.getTransaction().commit();
		HibernateUtil.shutdown();
	}
}

程序输出:

Hibernate: insert into Employee (EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?)
Hibernate: insert into ACCOUNT (ACC_NUMBER, employee_ID) values (?, ?)
Hibernate: insert into ACCOUNT (ACC_NUMBER, employee_ID) values (?, ?)
Hibernate: insert into Employee (EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?)
Hibernate: insert into ACCOUNT (ACC_NUMBER, employee_ID) values (?, ?)
Hibernate: update ACCOUNT set EMPLOYEE_ID=? where ID=?
Hibernate: update ACCOUNT set EMPLOYEE_ID=? where ID=?
Hibernate: update ACCOUNT set EMPLOYEE_ID=? where ID=?

2. 使用连接表 Hiberate 一对多映射

此方法使用连接表存储帐户和员工实体之间的关联。 @JoinTable注解已用于建立此关联。

2.1 设计

让我们看看数据库架构如何:

one To Many association in hiberate using join table

Hibernate 中的使用连接表的一对多关联

2.2 实体类

package hibernate.test.oneToMany.joinTable;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity(name = "JoinTableEmployeeEntity")
@Table(name = "Employee", uniqueConstraints = {
@UniqueConstraint(columnNames = "ID"),
@UniqueConstraint(columnNames = "EMAIL") })
public class EmployeeEntity implements Serializable
{
	private static final long serialVersionUID = -1798070786993154676L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	private Integer employeeId;

	@Column(name = "EMAIL", unique = true, nullable = false, length = 100)
	private String email;

	@Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
	private String firstName;

	@Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
	private String lastName;

	@OneToMany(cascade=CascadeType.ALL)
	@JoinTable(name="EMPLOYEE_ACCOUNT", joinColumns={@JoinColumn(name="EMPLOYEE_ID", referencedColumnName="ID")}
	, inverseJoinColumns={@JoinColumn(name="ACCOUNT_ID", referencedColumnName="ID")})
	private Set<AccountEntity> accounts;

	//Getters and setters
}

package hibernate.test.oneToMany.joinTable;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity(name = "JoinTableAccountEntity")
@Table(name = "ACCOUNT", uniqueConstraints = {
@UniqueConstraint(columnNames = "ID")})
public class AccountEntity implements Serializable
{

	private static final long serialVersionUID = -6790693372846798580L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	private Integer accountId;

	@Column(name = "ACC_NUMBER", unique = true, nullable = false, length = 100)
	private String accountNumber;

	//Getters and setters
}

2.3 Hiberate 配置

我们已经为运行时提供了两个实体,我们必须将它们添加到hibernate.cfg.xml文件中。 请注意,只能在配置文件中配置一组实体,否则可能会发生意外结果。

< ?xml version="1.0" encoding="utf-8"?>
< !DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
	<session-factory>
		<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernatetest</property>
		<property name="hibernate.connection.password">XXXXXX</property>
		<property name="hibernate.connection.username">root</property>
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
		<property name="show_sql">true</property>
		<property name="hbm2ddl.auto">create</property>
		<mapping clas="hibernate.test.oneToMany.foreignKeyAsso.AccountEntity"></mapping>
		<mapping clas="hibernate.test.oneToMany.foreignKeyAsso.EmployeeEntity"></mapping>
	</session-factory>
</hibernate-configuration>

2.4 演示

现在,该测试代码了。 我已经编写了以下代码来测试上述实体。

package hibernate.test.oneToMany;

import hibernate.test.HibernateUtil;
import hibernate.test.oneToMany.joinTable.AccountEntity;
import hibernate.test.oneToMany.joinTable.EmployeeEntity;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.Session;

public class TestJoinTable
{
	public static void main(String[] args)
	{
		Session session = HibernateUtil.getSessionFactory().openSession();
		session.beginTransaction();

		AccountEntity account1 = new AccountEntity();
		account1.setAccountNumber("123-345-65454");

		AccountEntity account2 = new AccountEntity();
		account2.setAccountNumber("123-345-6542222");

		//Add new Employee object
		EmployeeEntity emp = new EmployeeEntity();
		emp.setEmail("[email protected]");
		emp.setFirstName("demo");
		emp.setLastName("user");

		Set<AccountEntity> accounts = new HashSet<AccountEntity>();
		accounts.add(account1);
		accounts.add(account2);

		emp.setAccounts(accounts);
		//Save Employee
		session.save(emp);

		session.getTransaction().commit();
		HibernateUtil.shutdown();
	}
}

程序输出:

Hibernate: insert into Employee (EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?)
Hibernate: insert into ACCOUNT (ACC_NUMBER) values (?)
Hibernate: insert into ACCOUNT (ACC_NUMBER) values (?)
Hibernate: insert into EMPLOYEE_ACCOUNT (EMPLOYEE_ID, ACCOUNT_ID) values (?, ?)
Hibernate: insert into EMPLOYEE_ACCOUNT (EMPLOYEE_ID, ACCOUNT_ID) values (?, ?)

下载源码

在这个使用列表的 Hiberate 一对多映射注解示例的过程中,我们学习了使用外键关联和连接表技术在两个实体之间创建1..N关系。

学习愉快!

阅读更多:

Hiberate 一对一映射注解示例

Hiberate 多对多映射注解示例

Hiberate 多对多映射注解示例

原文: https://howtodoinjava.com/hibernate/hibernate-many-to-many-mapping-using-annotations/

在两个实体之间建立多对多 Hiberate 的映射,其中一个实体可以与多个其他实体实例相关。 例如,对于订阅服务,SubscriptionEntityReaderEntity可以是两种类型的实体。 任何订阅都可以有多个阅读器,其中一个阅读器可以订阅多个订阅。

在此 hibernate 教程中,我们将学习使用 hibernate 在数据库中创建多对多映射。

Table of contents

Hibernate many to many mapping design
Owner entity
Mapped entity
Configure entities in hibernate config file
Demo

1. Hiberate 多对多映射设计

为了演示使用 Hiberate 注解的多对多映射,我们将关联两个实体,即ReaderEntitySubscriptionEntity

他们的数据库架构应如下所示。 使用这些表,任何应用都可以保存读者和订阅之间的多个关联。

many-to-many-hibernate-mapping

2. 所有者实体

所有者实体是负责建立关联并维护它的实体。 在我们的情况下,我将ReaderEntity设置为所有者实体。 @JoinTable注解已用于建立此关联。

package hibernate.test.manyToMany.joinTable;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity(name = "ReaderEntity")
@Table(name = "READER", uniqueConstraints = {
		@UniqueConstraint(columnNames = "ID"),
		@UniqueConstraint(columnNames = "EMAIL") })

public class ReaderEntity implements Serializable 
{
	private static final long serialVersionUID = -1798070786993154676L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	private Integer readerId;

	@Column(name = "EMAIL", unique = true, nullable = false, length = 100)
	private String email;

	@Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
	private String firstName;

	@Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
	private String lastName;

	@ManyToMany(cascade=CascadeType.ALL)
	@JoinTable(name="READER_SUBSCRIPTIONS", joinColumns={@JoinColumn(referencedColumnName="ID")}
										, inverseJoinColumns={@JoinColumn(referencedColumnName="ID")})	
	private Set<SubscriptionEntity> subscriptions;

	//Getters and setters
}

3. 映射实体

我们的映射实体是SubscriptionEntity,它使用“mappingBy”属性映射到ReaderEntity

package hibernate.test.manyToMany.joinTable;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity(name = "SubscriptionEntity")
@Table(name = "SUBSCRIPTION", uniqueConstraints = {
		@UniqueConstraint(columnNames = "ID")})

public class SubscriptionEntity implements Serializable 
{
	private static final long serialVersionUID = -6790693372846798580L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	private Integer subscriptionId;

	@Column(name = "SUBS_NAME", unique = true, nullable = false, length = 100)
	private String subscriptionName;

	@ManyToMany(mappedBy="subscriptions")
	private Set<ReaderEntity> readers;

	//Getters and setters
}

4. 在 Hiberate 配置文件中配置实体

我们使两个实体都可以在运行时使用。 为此,我们必须将它们添加到hibernate.cfg.xml文件中。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernatetest</property>
        <property name="hibernate.connection.password">XXXXXX</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">true</property>
		<property name="hbm2ddl.auto">create</property>
        <mapping class="hibernate.test.manyToMany.joinTable.ReaderEntity"/>
        <mapping class="hibernate.test.manyToMany.joinTable.SubscriptionEntity"/>
    </session-factory>
</hibernate-configuration>

5. Hiberate 多对多注解映射示例

现在,该测试代码了。 我编写了以下代码来测试上述实体及其多对多关系。

package hibernate.test.manyToMany;

import hibernate.test.HibernateUtil;
import hibernate.test.manyToMany.joinTable.*;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.Session;

public class TestJoinTable
{
	public static void main(String[] args) 
	{
		Session session = HibernateUtil.getSessionFactory().openSession();
		session.beginTransaction();

		//Add subscription
		SubscriptionEntity subOne = new SubscriptionEntity();
		subOne.setSubscriptionName("Entertainment");

		SubscriptionEntity subTwo = new SubscriptionEntity();
		subTwo.setSubscriptionName("Horror");

		Set<SubscriptionEntity> subs = new HashSet<SubscriptionEntity>();
		subs.add(subOne);
		subs.add(subTwo);

		//Add readers
		ReaderEntity readerOne = new ReaderEntity();
		readerOne.setEmail("[email protected]");
		readerOne.setFirstName("demo");
		readerOne.setLastName("user");

		ReaderEntity readerTwo = new ReaderEntity();
		readerTwo.setEmail("[email protected]");
		readerTwo.setFirstName("demo");
		readerTwo.setLastName("user");

		Set<ReaderEntity> readers = new HashSet<ReaderEntity>();
		readers.add(readerOne);
		readers.add(readerTwo);

		readerOne.setSubscriptions(subs);
		readerTwo.setSubscriptions(subs);

		session.save(readerOne);
		session.save(readerTwo);

		session.getTransaction().commit();
		HibernateUtil.shutdown();
	}
}

程序输出:

Hibernate: insert into READER (EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?)
Hibernate: insert into SUBSCRIPTION (SUBS_NAME) values (?)
Hibernate: insert into SUBSCRIPTION (SUBS_NAME) values (?)
Hibernate: insert into READER (EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?)
Hibernate: insert into READER_SUBSCRIPTIONS (readers_ID, subscriptions_ID) values (?, ?)
Hibernate: insert into READER_SUBSCRIPTIONS (readers_ID, subscriptions_ID) values (?, ?)
Hibernate: insert into READER_SUBSCRIPTIONS (readers_ID, subscriptions_ID) values (?, ?)
Hibernate: insert into READER_SUBSCRIPTIONS (readers_ID, subscriptions_ID) values (?, ?)

下载源码

在此示例中,我们了解了在 Hiberate 中使用多对多注解来连接表

学习愉快!

Hiberate 一对一映射注解示例

原文: https://howtodoinjava.com/hibernate/hibernate-one-to-one-mapping-using-annotations/

如果您正在处理任何 Hiberate 项目,或者计划将来进行任何处理,那么您可以轻松了解应用中多个实体之间的一对一关系。 在此 Hibernate 一对一映射示例中,我们将讨论 Hibernate 支持的此映射的 3 种不同变体。

Table fo contents

Various supported techniques for one to one mapping
1\. Using foreign key association
2\. Using common join table
3\. Using shared primary key

对于此 Hiberate 一对一映射示例,我扩展了为 Hiberate HelloWorld 示例编写的示例。 这里有两个实体:EmployeeAccount

一名员工可以拥有一个帐户。 同样,一个帐户将仅与一个员工关联。 在此示例中,这是一对一的关系。

各种支持的技术

在 Hiberate 下,有 3 种方法可在两个实体之间创建一对一关系。 无论哪种方式,我们都必须使用@OneToOne注解。

  1. 第一种技术使用最广泛,并且在其中一个表中使用外键列
  2. 第二种技术使用一种众所周知的解决方案,即具有第三张表来存储前两个表之间的映射。
  3. 第三种技术是新技术,它在两个表中都使用了公共主键值

1. 使用外键关联的 Hiberate 一对一映射

在这种关联中,在所有者实体中创建了一个外键列。 例如,如果我们使为EmployeeEntity为所有者,则将在Employee表中创建一个额外的列"ACCOUNT_ID"。 此列将存储Account表的外键。

表结构将如下所示:

foreign key association one to one

要进行这种关联,请按如下所示引用EmployeeEntity类中的Account实体:


@OneToOne
@JoinColumn(name="ACCOUNT_ID")
private AccountEntity account;

连接列用@JoinColumn注解声明,看起来像 @Column 注解。 它还有一个名为referencedColumnName的参数。 此参数在目标实体中声明将用于连接的列。

如果在所有者方未声明@JoinColumn,则使用默认值。 将在所有者表中创建一个连接列,其名称将是所有者端关系名称_(下划线)和所拥有的主键列名称的连接。 侧。

在双向关系中,一方(只有一方)必须是所有者。 所有者负责关联列的更新。 为了声明对关系不负责的一方,使用了mappedBy属性mappedBy”是指所有者一方的关联的属性名称。


@OneToOne(mappedBy="account")
private EmployeeEntity employee;

在“mappedBy”属性上方,它声明依赖于所有者实体进行映射。

让我们在运行的代码中测试上述映射:


public class TestForeignKeyAssociation {

	public static void main(String[] args) {
		Session session = HibernateUtil.getSessionFactory().openSession();
		session.beginTransaction();

		AccountEntity account = new AccountEntity();
		account.setAccountNumber("123-345-65454");

		// Add new Employee object
		EmployeeEntity emp = new EmployeeEntity();
		emp.setEmail("[email protected]");
		emp.setFirstName("demo");
		emp.setLastName("user");

		// Save Account
		session.saveOrUpdate(account);
		// Save Employee
		emp.setAccount(account);
		session.saveOrUpdate(emp);

		session.getTransaction().commit();
		HibernateUtil.shutdown();
	}
}

运行以上代码可在数据库中创建所需的架构,然后运行这些 SQL 查询。


Hibernate: insert into ACCOUNT (ACC_NUMBER) values (?)
Hibernate: insert into Employee (ACCOUNT_ID, EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?, ?)

当您运行上述程序时,可以在两个表中验证数据和映射。

2. 使用公共连接表的 Hiberate 一对一映射

这种方法对我们所有人都不陌生。 让我们从这种技术的目标 DB 结构开始。

join table one to one mapping

在该技术中,要使用的主要注解是@JoinTable此注解用于定义两个表中的新表名(强制性)和外键。 让我们看看它的用法:

@OneToOne(cascade = CascadeType.ALL)
@JoinTable(name="EMPLOYEE_ACCCOUNT", joinColumns = @JoinColumn(name="EMPLOYEE_ID"),
inverseJoinColumns = @JoinColumn(name="ACCOUNT_ID"))
private AccountEntity account;

@JoinTable注解在EmployeeEntity类中使用。 它声明将使用两列EMPLOYEE_IDEMPLOYEE表的主键)和ACCOUNT_IDACCOUNT表的主键)创建一个新表EMPLOYEE_ACCOUNT

测试以上实体会在日志文件中生成以下 SQL 查询:

Hibernate: insert into ACCOUNT (ACC_NUMBER) values (?)
Hibernate: insert into Employee (EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?)
Hibernate: insert into EMPLOYEE_ACCCOUNT (ACCOUNT_ID, EMPLOYEE_ID) values (?, ?)

3. 使用共享主键的 Hiberate 一对一映射

在这种技术中,Hiberate 将确保在两个表中都使用一个公共的主键值。 这样,EmployeeEntity的主键也可以安全地假定为AccountEntity的主键。

表结构将如下所示:

shared primary key one to one

在这种方法中,@PrimaryKeyJoinColumn是要使用的主要注解。 让我们看看如何使用它。


@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
private AccountEntity account;

AccountEntity端,它将仍然依赖于所有者实体进行映射。


@OneToOne(mappedBy="account", cascade=CascadeType.ALL)
private EmployeeEntity employee;

测试以上实体会在日志文件中生成以下 SQL 查询:

Hibernate: insert into ACCOUNT (ACC_NUMBER) values (?)
Hibernate: insert into Employee (ACCOUNT_ID, EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?, ?)

因此,我们已经看到所有 3 种不同的方式来创建 Hiberate 中支持的一对一映射。 我建议您下载源代码并使用它。

学习愉快!

下载源码

JUnit5 教程

JUnit5 教程

原文: https://howtodoinjava.com/junit-5-tutorial/

这个 JUnit5 教程讨论了它如何适应 Java8 编码风格以及其他一些功能。 了解它与 JUnit 3 或 4 的区别。

JUnit5 是 Java 应用使用最广泛的测试框架。 长期以来,JUnit 一直在完美地完成其工作。 在这两者之间,JDK 8 带来了 Java 中非常令人兴奋的功能,最著名的是 lambda 表达式。 JUnit5 旨在适应 Java8 的编码风格以及其他一些功能,这就是为什么需要 Java8 在 JUnit5 中创建和执行测试(尽管可以执行用 JUnit 3 或 JUnit4 编写的测试以实现向后兼容) 。

Table of Contents

Architecture
Installation
Annotations
Writing Tests
Test Suites
Assertions
Assumptions
Backward Compatibility
Conclusion

JUnit5 架构

由于与 JUnit4 相比,JUnit5 由来自三个不同子项目的几个不同模块组成:

JUnit5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
  1. JUnit 平台

    为了能够启动 junit 测试,IDE,构建工具或插件需要包含和扩展平台 API。 它定义了TestEngine API,用于开发在平台上运行的新测试框架。
    它还提供了一个控制台启动器,可以从命令行启动平台并为 Gradle 和 Maven 构建插件。

  2. JUnit Jupiter

    它包括用于编写测试的新编程和扩展模型。 它具有所有新的 junit 注解和TestEngine实现,以运行使用这些注解编写的测试。

  3. JUnit Vintage

    其主要目的是支持在 JUnit5 平台上运行 JUnit 3 和 JUnit4 书面测试。 它具有向后兼容性。

安装

您可以在 Maven 或 Gradle 项目中使用 JUnit5,方法是至少包含两个依赖项,即 Jupiter 引擎依赖项和平台运行期依赖项。

//pom.xml

<properties>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<maven.compiler.source>1.8</maven.compiler.source>
	<maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
	<junit.jupiter.version>5.5.2</junit.jupiter.version>
	<junit.platform.version>1.5.2</junit.platform.version>
</properties>
<dependencies>
	<dependency>
		<groupId>org.junit.jupiter</groupId>
		<artifactId>junit-jupiter-engine</artifactId>
		<version>${junit.jupiter.version}</version>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>org.junit.platform</groupId>
		<artifactId>junit-platform-runner</artifactId>
		<version>${junit.platform.version}</version>
		<scope>test</scope>
	</dependency>
</dependencies>

//build.gradle

testRuntime("org.junit.jupiter:junit-jupiter-engine:5.5.2")
testRuntime("org.junit.platform:junit-platform-runner:1.5.2")

阅读更多: Maven 示例 | Gradle 示例

JUnit5 注解

JUnit5 提供了以下注解来编写测试。

注解 描述
@BeforeEach 带注解的方法将在测试类中的每个测试方法之前运行。
@AfterEach 带注解的方法将在测试类中的每个测试方法之后运行。
@BeforeAll 带注解的方法将在测试类中的所有测试方法之前运行。 此方法必须是静态的。
@AfterAll 带注解的方法将在测试类中的所有测试方法之后运行。 此方法必须是静态的。
@Test 用于将方法标记为 junit 测试
@DisplayName 用于为测试类或测试方法提供任何自定义显示名称
@Disable 它用于禁用或忽略测试套件中的测试类或方法。
@Nested 用于创建嵌套的测试类
@Tag 用标签标记测试方法或测试类,以进行测试发现和过滤
@TestFactory 标记方法为动态测试的测试工厂

在 JUnit5 中编写测试

在 JUnit4 和 JUnit5 之间,测试书写样式没有太大变化。 这是使用生命周期方法的示例测试。

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

import com.howtodoinjava.junit5.examples.Calculator;

public class AppTest {

	@BeforeAll
	static void setup(){
		System.out.println("@BeforeAll executed");
	}

	@BeforeEach
	void setupThis(){
		System.out.println("@BeforeEach executed");
	}

	@Tag("DEV")
	@Test
    void testCalcOne() 
	{
		System.out.println("======TEST ONE EXECUTED=======");
		Assertions.assertEquals( 4 , Calculator.add(2, 2));
    }

	@Tag("PROD")
	@Disabled
	@Test
    void testCalcTwo() 
	{
		System.out.println("======TEST TWO EXECUTED=======");
		Assertions.assertEquals( 6 , Calculator.add(2, 4));
    }

	@AfterEach
	void tearThis(){
		System.out.println("@AfterEach executed");
	}

	@AfterAll
	static void tear(){
		System.out.println("@AfterAll executed");
	}
}

测试套件

使用 JUnit5 测试套件,您可以运行分散到多个测试类和不同包中的测试。 JUnit5 提供了两个注解:@SelectPackages@SelectClasses以创建测试套件。

要执行套件,您将使用@RunWith(JUnitPlatform.class)

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
public class JUnit5TestSuiteExample 
{
}

此外,您可以使用以下注解来过滤测试包,类甚至测试方法。

  1. @IncludePackages@ExcludePackages过滤包
  2. @IncludeClassNamePatterns@ExcludeClassNamePatterns过滤测试类
  3. @IncludeTags@ExcludeTags过滤测试方法
@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
@IncludePackages("com.howtodoinjava.junit5.examples.packageC")
@ExcludeTags("PROD")
public class JUnit5TestSuiteExample 
{
}

阅读更多: JUnit5 测试套件

断言

断言有助于通过测试用例的实际输出来验证期望的输出。 为简单起见,所有 JUnit Jupiter 断言都是org.junit.jupiter.Assertions类中的静态方法,例如assertEquals()assertNotEquals()

void testCase() 
{
    //Test will pass
    Assertions.assertNotEquals(3, Calculator.add(2, 2));

    //Test will fail 
    Assertions.assertNotEquals(4, Calculator.add(2, 2), "Calculator.add(2, 2) test failed");

    //Test will fail 
    Supplier<String> messageSupplier  = ()-> "Calculator.add(2, 2) test failed";
    Assertions.assertNotEquals(4, Calculator.add(2, 2), messageSupplier);
}

阅读更多: JUnit5 断言

假设

Assumptions类提供静态方法来支持基于假设的条件测试执行。 假设失败会导致测试中止。 通常在没有必要继续执行给定测试方法的情况下使用假设。 在测试报告中,这些测试将被标记为通过。

JUnit jupiter Assumptions类具有两个此类方法:assumeFalse()assumeTrue()

public class AppTest {
    @Test
    void testOnDev() 
    {
        System.setProperty("ENV", "DEV");
        Assumptions.assumeTrue("DEV".equals(System.getProperty("ENV")), AppTest::message);
    }

    @Test
    void testOnProd() 
    {
        System.setProperty("ENV", "PROD");
        Assumptions.assumeFalse("DEV".equals(System.getProperty("ENV")));  
    }

    private static String message () {
        return "TEST Execution Failed :: ";
    }
}

阅读更多: JUnit5 假设

JUnit 3 或 JUnit4 的向后兼容性

JUnit4 已经存在了很长时间,并且用 JUnit4 编写了许多测试。JUnitJupiter 也需要支持这些测试。 为此,开发了 JUnit Vintage 子项目。

JUnit Vintage 提供了TestEngine实现,用于在 JUnit5 平台上运行基于 JUnit 3 和 JUnit4 的测试。

总结

JUnit5 仍在开发中。 看起来如此令人兴奋且功能丰富。 现在,它可以通过第三方工具和 API 进行扩展。 作为一名测试作者,您可能不会有太大的不同,但是当您尝试扩展它或尝试开发任何 IDE 插件时,您会称赞它。

作为开发人员,您还可以考虑将测试模板添加到 Eclipse IDE 中以提高开发速度。

在评论中写下您对此 JUnit5 教程的想法。

学习愉快!

源码下载

多模块 Maven 项目 – 控制台

原文: https://howtodoinjava.com/maven/multi-module-project-console/

了解如何从控制台 IDE 创建多模块 Maven 项目。 在此 maven 教程中,我们将学习使用 maven cli 命令创建嵌套的 maven 项目

1. Maven 项目

1.1 Maven 父项目

父 maven 项目的包类型为pom。 它使项目成为一个聚合器 – 不会产生进一步的工件。

1.2 Maven 子项目/模块

  • Maven 子项目是独立的 Maven 项目,但从父项目继承属性。
  • 可以使用单个命令来构建父项目中的所有子项目。
  • 定义项目之间的关系更加容易。 如 JAR 项目可以打包成 WAR 项目。

2. 从控制台创建多模块 Maven 项目

让我们创建一个 maven 项目,其中包含 ear,war 和 jar 类型的模块。 注意如何将archetypeArtifactId设置为pom-root

Projects Relationship

项目关系

1.1 创建父项目 - 打包类型为pom

创建父项目将在命令下方。

$ mvn archetype:generate -DgroupId=com.howtodoinjava 
						-DartifactId=HelloWorldApp 
						-DarchetypeArtifactId=maven-archetype-quickstart 
						-DinteractiveMode=false

打开pom.xml文件,并将包类型更改为'pom

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.howtodoinjava</groupId>
	<artifactId>HelloWorldApp</artifactId>

	<!-- chaged fom jar to pom -->
	<packaging>pom</packaging>

	<version>1.0-SNAPSHOT</version>
	<name>HelloWorldApp</name>
	<url>http://maven.apache.org</url>
</project>

1.2 创建 EAR,网络和服务模块

在控制台中,导航到父项目文件夹并为服务(通用代码,例如 DAO),rws(REST 服务或 Web 组件)和 Ear 创建其他模块。

$ cd HelloWorldApp

$ mvn archetype:generate -DgroupId=com.howtodoinjava 
						-DartifactId=HelloWorldApp-ear 
						-DarchetypeArtifactId=maven-archetype-quickstart 
						-DinteractiveMode=false

$ mvn archetype:generate -DgroupId=com.howtodoinjava 
						-DartifactId=HelloWorldApp-service 
						-DarchetypeArtifactId=maven-archetype-quickstart 
						-DinteractiveMode=false

$ mvn archetype:generate -DgroupId=com.howtodoinjava 
						-DartifactId=HelloWorldApp-rws 
						-DarchetypeArtifactId=maven-archetype-webapp 
						-DinteractiveMode=false

1.3 更新 Maven 插件和依赖项

现在该更新添加依赖项(例如,将服务添加到 war 文件中,并将 war 文件添加到 ear 文件中)。 同样,所有用于构建 Ear 和 War 文件工件的 Maven 插件。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
	http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.howtodoinjava</groupId>
	<artifactId>HelloWorldApp</artifactId>
	<packaging>pom</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>HelloWorldApp</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>

	<modules>
		<module>HelloWorldApp-ear</module>
		<module>HelloWorldApp-service</module>
		<module>HelloWorldApp-rws</module>
	</modules>

</project>

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
	http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.howtodoinjava</groupId>
		<artifactId>HelloWorldApp</artifactId>
		<version>1.0-SNAPSHOT</version>
	</parent>
	<artifactId>HelloWorldApp-ear</artifactId>
	<name>HelloWorldApp-ear</name>
	<url>http://maven.apache.org</url>

	<packaging>ear</packaging>

	<dependencies>
		<dependency>
			<groupId>com.howtodoinjava</groupId>
			<artifactId>HelloWorldApp-rws</artifactId>
			<version>1.0-SNAPSHOT</version>
			<type>war</type>
		</dependency>
	</dependencies>

	<build>
	<pluginManagement>
	<plugins>
	<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-ear-plugin</artifactId>
		<version>3.0.1</version>
		<configuration>
			<modules>
				<webModule>
					<groupId>com.howtodoinjava</groupId>
					<artifactId>HelloWorldApp-rws</artifactId>
					<uri>HelloWorldApp-rws-1.0-SNAPSHOT.war</uri>
					<!-- Set custom context root -->
					<contextRoot>/application</contextRoot>
				</webModule>
			</modules>
		</configuration>
	</plugin>
	</plugins>
	</pluginManagement>
	</build>

</project>

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
	http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>com.howtodoinjava</groupId>
		<artifactId>HelloWorldApp</artifactId>
		<version>1.0-SNAPSHOT</version>
	</parent>

	<artifactId>HelloWorldApp-rws</artifactId>
	<packaging>war</packaging>
	<name>HelloWorldApp-rws Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<dependencies>
		 <dependency>
            <groupId>com.howtodoinjava</groupId>
            <artifactId>HelloWorldApp-service</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
	</dependencies>

	<build>
		<finalName>HelloWorldApp-rws</finalName>
	</build>
</project>

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
	http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>com.howtodoinjava</groupId>
		<artifactId>HelloWorldApp</artifactId>
		<version>1.0-SNAPSHOT</version>
	</parent>

	<artifactId>HelloWorldApp-service</artifactId>
	<name>HelloWorldApp-service</name>
	<url>http://maven.apache.org</url>
</project>

阅读更多:如何在 Eclipse 中创建多模块 Maven 项目

2. 项目构建过程

要生成项目,请从控制台运行$mvn clean install命令。

E:\devsetup\workspacetemp\HelloWorldApp>mvn clean install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] HelloWorldApp                                                      [pom]
[INFO] HelloWorldApp-service                                              [jar]
[INFO] HelloWorldApp-rws Maven Webapp                                     [war]
[INFO] HelloWorldApp-ear                                                  [jar]
[INFO]

[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ HelloWorldApp-service ---
[WARNING] JAR will be empty - no content was marked for inclusion!
[INFO] Building jar: E:\devsetup\workspacetemp\HelloWorldApp\HelloWorldApp-service\target\HelloWorldApp-service-1.0-SNAPSHOT.jar
[INFO]

[INFO] Copying webapp resources [E:\devsetup\workspacetemp\HelloWorldApp\HelloWorldApp-rws\src\main\webapp]
[INFO] Webapp assembled in [47 msecs]
[INFO] Building war: E:\devsetup\workspacetemp\HelloWorldApp\HelloWorldApp-rws\target\HelloWorldApp-rws.war
[INFO] WEB-INF\web.xml already added, skipping
[INFO]

[INFO] --- maven-install-plugin:2.4:install (default-install) @ HelloWorldApp-ear ---
[INFO] Installing E:\devsetup\workspacetemp\HelloWorldApp\HelloWorldApp-ear\target\HelloWorldApp-ear-1.0-SNAPSHOT.ear to E:\devsetup\M2\com\howtodoinjava\HelloWorldApp-ear\1.0-SNAPSHOT\HelloWorldApp-ear-1.0-SNAPSHOT.ear

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] HelloWorldApp 1.0-SNAPSHOT ......................... SUCCESS [  0.324 s]
[INFO] HelloWorldApp-service .............................. SUCCESS [  0.894 s]
[INFO] HelloWorldApp-rws Maven Webapp ..................... SUCCESS [  0.531 s]
[INFO] HelloWorldApp-ear 1.0-SNAPSHOT ..................... SUCCESS [  0.565 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.487 s
[INFO] Finished at: 2018-11-18T22:44:44+05:30
[INFO] ------------------------------------------------------------------------

构建会生成名称为HelloWorldApp-ear-1.0-SNAPSHOT.ear的 ear 文件。 可以根据需要随意更改项目名称和生成的包。

学习愉快!

JUnit5 测试生命周期

原文: https://howtodoinjava.com/junit5/junit-5-test-lifecycle/

在 JUnit5 中,测试生命周期由 4 个主要注解驱动,即@BeforeAll@BeforeEach@AfterEach@AfterAll。 同时,每个测试方法都必须标有@Test注解。 尽管@Test注解不再使用可选参数,但它几乎没有变化。

之前和之后

单元测试生命周期中,您首先需要使用一些方法来设置和拆除运行测试的环境或测试数据。

在 JUnit 中,为每个测试创建一个新的测试实例。 @BeforeAll@AfterAll注解(按名称清除)在整个测试执行周期中仅应调用一次。 因此,必须将它们声明为static

如果它们是具有相同注解的多个方法(例如,两个具有@BeforeAll的方法),则它们的执行顺序不确定。

对于每个测试实例都调用@BeforeEach@AfterEach,因此不必是static

public class AppTest {

	@BeforeAll
	static void setup(){
		System.out.println("@BeforeAll executed");
	}

	@BeforeEach
	void setupThis(){
		System.out.println("@BeforeEach executed");
	}

	@Test
    void testCalcOne() 
	{
		System.out.println("======TEST ONE EXECUTED=======");
		Assertions.assertEquals( 4 , Calculator.add(2, 2));
    }

	@Test
    void testCalcTwo() 
	{
		System.out.println("======TEST TWO EXECUTED=======");
		Assertions.assertEquals( 6 , Calculator.add(2, 4));
    }

	@AfterEach
	void tearThis(){
		System.out.println("@AfterEach executed");
	}

	@AfterAll
	static void tear(){
		System.out.println("@AfterAll executed");
	}
}

控制台输出:

@BeforeAll executed

@BeforeEach executed
======TEST ONE EXECUTED=======
@AfterEach executed

@BeforeEach executed
======TEST TWO EXECUTED=======
@AfterEach executed

@AfterAll executed

禁用测试

要在 JUnit5 中禁用测试,您将需要使用@Disabled注解。 它等效于 JUnit4 的@Ignored注解。

@Disabled注解可以应用于测试类(禁用该类中的所有测试方法)或单个测试方法。

@Disabled
@Test
void testCalcTwo() 
{
	System.out.println("======TEST TWO EXECUTED=======");
	Assertions.assertEquals( 6 , Calculator.add(2, 4));
}

断言

在任何测试方法中,我们都需要确定它是否通过失败。 您可以使用断言进行操作。 断言有助于通过测试用例的实际输出来验证期望的输出。 为简单起见,所有 JUnit Jupiter 断言都是org.junit.jupiter.Assertions类中的静态方法。

void testCase() 
{
    //Test will pass
    Assertions.assertEquals(4, Calculator.add(2, 2));

    //Test will fail 
    Assertions.assertEquals(3, Calculator.add(2, 2), "Calculator.add(2, 2) test failed");

    //Test will fail 
    Supplier<String> messageSupplier  = ()-> "Calculator.add(2, 2) test failed";
    Assertions.assertEquals(3, Calculator.add(2, 2), messageSupplier);
}

要使测试失败,只需使用Assertions.fail()方法。

@Test
void testCase() {

    Assertions.fail("not found good reason to pass");
}

假设

假设提供了静态方法来支持基于假设的条件测试执行。 假设失败会导致测试中止。 通常在没有必要继续执行给定测试方法的情况下使用假设。 在测试报告中,这些测试将被标记为通过。

假设类有两种方法:assumeFalse()assumeTrue()。 第三种方法assumeThat()处于实验状态,可能会在将来的版本中得到确认。

@Test
void testOnDev() 
{
    System.setProperty("ENV", "DEV");
    Assumptions.assumeTrue("DEV".equals(System.getProperty("ENV")));
    //remainder of test will proceed
}

@Test
void testOnProd() 
{
    System.setProperty("ENV", "PROD");
    Assumptions.assumeTrue("DEV".equals(System.getProperty("ENV")));
    //remainder of test will be aborted
}

junit 测试生命周期的所有上述注解和类。 将我的问题放在评论部分。

学习愉快!

源码下载

JUnit5 @BeforeAll注解示例

原文: https://howtodoinjava.com/junit5/before-all-annotation-example/

JUnit5 @BeforeAll 注解替换了 JUnit4 中的@BeforeClass注解。 它用于表示应在当前测试类中的所有测试之前执行注解方法

@BeforeAll注解用法

使用@BeforeAll注解方法,如下所示:

@BeforeAll
public static void init(){
	System.out.println("BeforeAll init() method called");
}

@BeforeAll注解的方法必须是静态方法,否则它将引发运行时错误。

org.junit.platform.commons.JUnitException: @BeforeAll method 'public void com.howtodoinjava.junit5.examples.JUnit5AnnotationsExample.init()' must be static.
at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.assertStatic(LifecycleMethodUtils.java:66)
at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.lambda$findBeforeAllMethods$0(LifecycleMethodUtils.java:42)
at java.util.ArrayList.forEach(ArrayList.java:1249)
at java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1080)
at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeAllMethods(LifecycleMethodUtils.java:42)

@BeforeAll注解示例

让我们举个例子。 我使用了一个Calculator类并添加了一个add方法。 我将使用@RepeatedTest注解对其进行 5 次测试。 此注解将导致add测试运行 5 次。 但是@BeforeAll带注解的方法只能调用一次。

package com.howtodoinjava.junit5.examples;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
public class BeforeAllTest {

	@DisplayName("Add operation test")
	@RepeatedTest(5)
	void addNumber(TestInfo testInfo, RepetitionInfo repetitionInfo) {
		System.out.println("Running test -> " + repetitionInfo.getCurrentRepetition());
		Assertions.assertEquals(2, Calculator.add(1, 1), "1 + 1 should equal 2");
	}

	@BeforeAll
	public static void init(){
		System.out.println("Before All init() method called");
	}
}

其中Calculator类是:

package com.howtodoinjava.junit5.examples;

public class Calculator 
{
	public int add(int a, int b) {
		return a + b;
	}
}

现在执行测试,您将看到以下控制台输出:

Before All init() method called
Running test -> 1
Running test -> 2
Running test -> 3
Running test -> 4
Running test -> 5

显然,带注解的@BeforeAll方法init()仅被调用一次。

学习愉快!

源码下载

JUnit5 @BeforeEach注解示例

原文: https://howtodoinjava.com/junit5/before-each-annotation-example/

JUnit5 @BeforeEach注解替换了 JUnit4 中的@Before注解。它用于表示应在当前类中的每个@Test方法之前执行注解方法

@BeforeEach注解用法

使用@BeforeEach注解方法,如下所示:

@BeforeEach
public void initEach(){
	System.out.println("Before Each initEach() method called");
}

@BeforeEach注解的方法不得为静态方法,否则它将引发运行时错误。

org.junit.platform.commons.JUnitException: @BeforeEach method 'public static void com.howtodoinjava.junit5.examples.JUnit5AnnotationsExample.initEach()' must not be static.
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.assertNonStatic(LifecycleMethodUtils.java:73)
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.lambda$findBeforeEachMethods$2(LifecycleMethodUtils.java:54)
	at java.util.ArrayList.forEach(ArrayList.java:1249)
	at java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1080)
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeEachMethods(LifecycleMethodUtils.java:54)

@BeforeEach注解示例

让我们举个例子。 我使用了一个Calculator类并添加了一个add方法。 我将使用@RepeatedTest注解对其进行 5 次测试。 此注解将导致add测试运行 5 次。 对于每次运行的测试方法,@BeforeEach带注解的方法也应每次运行。

package com.howtodoinjava.junit5.examples;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
public class BeforeEachTest {

	@DisplayName("Add operation test")
	@RepeatedTest(5)
	void addNumber(TestInfo testInfo, RepetitionInfo repetitionInfo) {
		System.out.println("Running test -> " + repetitionInfo.getCurrentRepetition());
		Assertions.assertEquals(2, Calculator.add(1, 1), "1 + 1 should equal 2");
	}

	@BeforeAll
	public static void init(){
		System.out.println("BeforeAll init() method called");
	}

	@BeforeEach
	public void initEach(){
		System.out.println("BeforeEach initEach() method called");
	}
}

其中Calculator类是:

package com.howtodoinjava.junit5.examples;

public class Calculator 
{
	public int add(int a, int b) {
		return a + b;
	}
}

现在执行测试,您将看到以下控制台输出:

BeforeAll init() method called
BeforeEach initEach() method called

BeforeEach initEach() method called
Running test -> 1

BeforeEach initEach() method called
Running test -> 2

BeforeEach initEach() method called
Running test -> 3

BeforeEach initEach() method called
Running test -> 4

BeforeEach initEach() method called
Running test -> 5

显然,每次测试方法调用时都调用@BeforeEach注解的initEach()方法。

学习愉快!

源码下载

JUnit5 @AfterEach注解示例

原文: https://howtodoinjava.com/junit5/after-each-annotation-example/

JUnit5 @AfterEach 注解替换了 JUnit4 中的@After注解。它用于表示应在当前类中的每个@Test方法之后执行注解方法

@AfterEach注解用法

使用@AfterEach注解方法,如下所示:

@AfterEach
public void cleanUpEach(){
	System.out.println("After Each cleanUpEach() method called");
}

带注解的@AfterEach方法不得为静态方法,否则它将引发运行时错误。

org.junit.platform.commons.JUnitException: @AfterEach method 'public static void com.howtodoinjava.junit5.examples.JUnit5AnnotationsExample.cleanUpEach()' must not be static.
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.assertNonStatic(LifecycleMethodUtils.java:73)
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.lambda$findAfterEachMethods$3(LifecycleMethodUtils.java:60)
	at java.util.ArrayList.forEach(ArrayList.java:1249)
	at java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1080)
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterEachMethods(LifecycleMethodUtils.java:60)

@AfterEach注解示例

让我们举个例子。 我使用了一个Calculator类并添加了一个add方法。 我将使用@RepeatedTest注解对其进行 5 次测试。 此注解将导致add测试运行 5 次。 对于每次运行的测试方法,@AfterEach带注解的方法也应每次运行。

package com.howtodoinjava.junit5.examples;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
public class AfterAnnotationsTest {

	@DisplayName("Add operation test")
	@RepeatedTest(5)
	void addNumber(TestInfo testInfo, RepetitionInfo repetitionInfo) 
	{
		System.out.println("Running test -> " + repetitionInfo.getCurrentRepetition());
		Assertions.assertEquals(2, Calculator.add(1, 1), "1 + 1 should equal 2");
	}

	@AfterAll
	public static void cleanUp(){
		System.out.println("After All cleanUp() method called");
	}

	@AfterEach
	public void cleanUpEach(){
		System.out.println("After Each cleanUpEach() method called");
	}
}

其中Calculator类是:

package com.howtodoinjava.junit5.examples;

public class Calculator 
{
	public int add(int a, int b) {
		return a + b;
	}
}

现在执行测试,您将看到以下控制台输出:

Running test -> 1
After Each cleanUpEach() method called

Running test -> 2
After Each cleanUpEach() method called

Running test -> 3
After Each cleanUpEach() method called

Running test -> 4
After Each cleanUpEach() method called

Running test -> 5
After Each cleanUpEach() method called

After All cleanUp() method called

显然,每次测试方法调用时都将调用@AfterEach注解的cleanUpEach()方法。

学习愉快!

源码下载

JUnit5 @AfterAll注解示例

原文: https://howtodoinjava.com/junit5/after-all-annotation-example/

JUnit5 @AfterAll 注解替换了 JUnit4 中的@AfterClass注解。它用于表示应在当前测试类中的所有测试之后执行注解方法

@AfterAll注解用法

使用@AfterAll注解方法,如下所示:

@AfterAll
public static void cleanUp(){
	System.out.println("After All cleanUp() method called");
}

带注解的@AfterAll方法必须是静态方法,否则它将引发运行时错误。

org.junit.platform.commons.JUnitException: @AfterAll method 'public void com.howtodoinjava.junit5.examples.JUnit5AnnotationsExample.cleanUp()' must be static.
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.assertStatic(LifecycleMethodUtils.java:66)
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.lambda$findAfterAllMethods$1(LifecycleMethodUtils.java:48)
	at java.util.ArrayList.forEach(ArrayList.java:1249)
	at java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1080)
	at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterAllMethods(LifecycleMethodUtils.java:48)

@AfterAll注解示例

让我们举个例子。 我使用了一个Calculator类并添加了一个add方法。 我将使用@RepeatedTest注解对其进行 5 次测试。 此注解将导致add测试运行 5 次。 但是@AfterAll带注解的方法只能调用一次。

package com.howtodoinjava.junit5.examples;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
public class AfterAnnotationsTest {

	@DisplayName("Add operation test")
	@RepeatedTest(5)
	void addNumber(TestInfo testInfo, RepetitionInfo repetitionInfo) 
	{
		System.out.println("Running test -> " + repetitionInfo.getCurrentRepetition());
		Assertions.assertEquals(2, Calculator.add(1, 1), "1 + 1 should equal 2");
	}

	@AfterAll
	public static void cleanUp(){
		System.out.println("After All cleanUp() method called");
	}

	@AfterEach
	public void cleanUpEach(){
		System.out.println("After Each cleanUpEach() method called");
	}
}

其中Calculator类是:

package com.howtodoinjava.junit5.examples;

public class Calculator 
{
	public int add(int a, int b) {
		return a + b;
	}
}

现在执行测试,您将看到以下控制台输出:

Running test -> 1
After Each cleanUpEach() method called

Running test -> 2
After Each cleanUpEach() method called

Running test -> 3
After Each cleanUpEach() method called

Running test -> 4
After Each cleanUpEach() method called

Running test -> 5
After Each cleanUpEach() method called

After All cleanUp() method called

显然,带注解的@AfterAll方法cleanUp()仅被调用一次。

学习愉快!

源码下载

JUnit5 @RepeatedTest注解示例

原文: https://howtodoinjava.com/junit5/repeated-test-annotation-example/

通过 JUnit5 @RepeatedTest注解,可以编写可以多次运行的可重复测试模板。 频率可以配置为@RepeatedTest注解的参数。

1. @RepeatedTest注解用法

要创建可重复的测试模板方法,请使用@RepeatedTest注解该方法。

@DisplayName("Add operation test")
@RepeatedTest(5)
void addNumber(TestInfo testInfo) {
	Calculator calculator = new Calculator();
	Assertions.assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2");
}

在上面的代码中,addNumber()测试将重复 5 次。

请注意,每次重复测试的行为都类似于常规@Test方法的执行,并且完全支持相同的生命周期回调和扩展。 这意味着对于每个单独的调用,将在适合它们的测试生命周期的地方调用@BeforeEach@AfterEach带注解的方法。

package com.howtodoinjava.junit5.examples;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
public class RepeatedTestExample {

	@BeforeAll
	public static void init(){
		System.out.println("Before All init() method called");
	}

	@BeforeEach
	public void initEach(){
		System.out.println("Before Each initEach() method called");
	}

	@DisplayName("Add operation test")
	@RepeatedTest(5)
	void addNumber(TestInfo testInfo, RepetitionInfo repetitionInfo) 
	{
		System.out.println("Running addNumber test -> " + repetitionInfo.getCurrentRepetition());
		Assertions.assertEquals(2, Calculator.add(1, 1), "1 + 1 should equal 2");
	}

	@AfterEach
	public void cleanUpEach(){
		System.out.println("After Each cleanUpEach() method called");
	}

	@AfterAll
	public static void cleanUp(){
		System.out.println("After All cleanUp() method called");
	}
}

上面程序的输出:

Before All init() method called

Before Each initEach() method called
After Each cleanUpEach() method called

Before Each initEach() method called
Running addNumber test -> 1
After Each cleanUpEach() method called

Before Each initEach() method called
Running addNumber test -> 2
After Each cleanUpEach() method called

Before Each initEach() method called
Running addNumber test -> 3
After Each cleanUpEach() method called

Before Each initEach() method called
Running addNumber test -> 4
After Each cleanUpEach() method called

Before Each initEach() method called
Running addNumber test -> 5
After Each cleanUpEach() method called

After All cleanUp() method called

2. 自定义显示测试名称

除了指定重复次数之外,您还可以为每个重复指定自定义显示名称。 此自定义显示名称可以是静态文本+动态占位符的组合。 当前,支持 3 个占位符:

  • {displayName}@RepeatedTest方法的显示名称。
  • {currentRepetition}:当前重复计数。
  • {totalRepetitions}:重复总数。
@RunWith(JUnitPlatform.class)
public class JUnit5AnnotationsExample 
{
	@DisplayName("Add operation test")
	@RepeatedTest(value = 5, name = "{displayName} - repetition {currentRepetition} of {totalRepetitions}")
	void addNumber(TestInfo testInfo) {
		Assertions.assertEquals(2, Calculator.add(1, 1), "1 + 1 should equal 2");
	}
}

运行以上测试将在下面输出:

JUnit5 Repeated Test Display Names

JUnit5 重复测试的显示名称

您可以使用两种预定义格式之一,即RepeatedTest.LONG_DISPLAY_NAMERepeatedTest.SHORT_DISPLAY_NAME。 如果未指定,则SHORT_DISPLAY_NAME是默认格式。

  • RepeatedTest.LONG_DISPLAY_NAME{displayName} :: repetition {currentRepetition} of {totalRepetitions}
  • RepeatedTest.SHORT_DISPLAY_NAMErepetition {currentRepetition} of {totalRepetitions}
@DisplayName("Add operation test")
@RepeatedTest(value = 5, name = RepeatedTest.LONG_DISPLAY_NAME)
void addNumber(TestInfo testInfo) {
	Assertions.assertEquals(2, Calculator .add(1, 1), "1 + 1 should equal 2");
}

3. RepetitionInfo接口

RepetitionInfo用于将有关重复测试的当前重复的信息注入@RepeatedTest@BeforeEach@AfterEach方法中。

@RunWith(JUnitPlatform.class)
public class JUnit5AnnotationsExample {

	@BeforeEach
	public void initEach(RepetitionInfo info){
		int currentRepetition = info.getCurrentRepetition();
        int totalRepetitions = info.getTotalRepetitions();
        //Use information as needed
	}

	@DisplayName("Add operation test")
	@RepeatedTest(value = 5, name="{displayName} :: repetition {currentRepetition} of {totalRepetitions}")
	void addNumber(TestInfo testInfo) {
		Calculator calculator = new Calculator();
		Assertions.assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2");
	}

	@AfterEach
	public void cleanUpEach(RepetitionInfo info){
		int currentRepetition = info.getCurrentRepetition();
        int totalRepetitions = info.getTotalRepetitions();
        //Use information as needed
	}
}

将我的问题放在评论部分。

学习愉快!

源码下载

JUnit5 @Disabled测试示例

原文: https://howtodoinjava.com/junit5/junit-5-disabled-test-example/

JUnit @Disabled注解可用于禁用测试套件中的测试方法。 该注解可以应用于测试类以及各个测试方法。

它仅接受一个可选参数,它指示此测试被禁用的原因。

@Disabled的测试类

@Disabled应用于测试类时,该类中的所有测试方法也会自动禁用

import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

@Disabled
public class AppTest {

	@Test
    void testOnDev() 
	{
		System.setProperty("ENV", "DEV");
        Assumptions.assumeFalse("DEV".equals(System.getProperty("ENV")));
    }

	@Test
    void testOnProd() 
	{
		System.setProperty("ENV", "PROD");
        Assumptions.assumeFalse("DEV".equals(System.getProperty("ENV")));
    }
}

JUnit5 Disabled Annotation Over Class

JUnit5 类上的@Disabled注解

请注意运行次数:2/2(跳过 2 个)。 显然,两个测试都被禁用,因此不执行。

JUnit5 @Disabled测试方法

@Disabled用于表示当前已禁用带注解的测试方法,因此不应执行

import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

public class AppTest {

	@Disabled("Do not run in lower environment")
	@Test
    void testOnDev() 
	{
		System.setProperty("ENV", "DEV");
        Assumptions.assumeFalse("DEV".equals(System.getProperty("ENV")));
    }

	@Test
    void testOnProd() 
	{
		System.setProperty("ENV", "PROD");
        Assumptions.assumeFalse("DEV".equals(System.getProperty("ENV")));
    }
}

JUnit5 Disabled Annotation Over Method

JUnit5 方法上的@Disabled注解

将我的问题放在评论部分。

学习愉快!

源码下载

JUnit5 @Tag注解示例

原文: https://howtodoinjava.com/junit5/junit-5-tag-annotation-example/

JUnit5 @Tag 可用于从测试计划中过滤测试用例。 它可以帮助针对不同的环境,不同的用例或任何特定要求创建多个不同的测试计划。 您可以通过仅在测试计划中包括那些标记的测试或通过从测试计划中排除其他测试来执行测试集。

1. @Tag注解用法

  1. 我们可以将其应用于测试类或测试方法或同时应用

    @Tag("development")
    public class ClassATest
    {
    	@Test
    	@Tag("userManagement")
    	void testCaseA(TestInfo testInfo) {
    	}
    }
    
    
  2. 我们也可以将多个标签应用于单个测试案例,以便您可以将其包含在多个测试计划中。

    public class ClassATest
    {
    	@Test
    	@Tag("development")
    	@Tag("production")
    	void testCaseA(TestInfo testInfo) {
    	}
    }
    
    

2. 使用@IncludeTags@ExcludeTags创建测试计划

我们可以在测试计划中使用@IncludeTags@ExcludeTags注解来过滤测试或包括测试。

//@IncludeTags example

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
@IncludeTags("production")
public class MultipleTagsExample 
{
}

//@ExcludeTags example

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
@ExcludeTags("production")
public class MultipleTagsExample 
{
}

要添加多个标签,请在所需注解中传递标签的字符串数组

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
@IncludeTags({"production","development"})
public class MultipleTagsExample 
{
}

我们不能在单个测试计划中同时包含@IncludeTags@ExcludeTags 注解。

3. JUnit5 @Tag示例

假设我们有 3 个测试,并且我们想在开发环境中运行全部 3 个测试; 但只想在生产中运行一个。 因此,我们将标记测试如下:

public class ClassATest
{
	@Test
	@Tag("development")
	@Tag("production")
	void testCaseA(TestInfo testInfo) { //run in all environments
	}
}

public class ClassBTest
{
	@Test
	@Tag("development")
	void testCaseB(TestInfo testInfo) {
	}
}

public class ClassCTest
{
	@Test
	@Tag("development")
	void testCaseC(TestInfo testInfo) {
	}
}

让我们为两种环境创建测试计划。

在生产环境中运行测试

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
@IncludeTags("production")
public class ProductionTests 
{
}

JUnit5 @Tag Example - Production Tests

JUnit5 @Tag示例 – 生产测试

在开发环境中运行测试

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
@IncludeTags("development")
public class DevelopmentTests 
{
}

JUnit5 @Tag Example - Development Tests

JUnit5 @Tag示例 – 开发测试

将我的问题放在评论部分。

学习愉快!

源码下载

JUnit5 预期的异常 – assertThrows()示例

原文: https://howtodoinjava.com/junit5/expected-exception-example/

在 JUnit5 中,要测试异常情况,则应使用org.junit.jupiter.api.Assertions.assertThrows()方法。 JUnit5 异常测试还有其他方法,但我建议避免使用它们。

1. JUnit5 assertThrows()的语法

它断言所提供的executable的执行将引发expectedType的异常并返回该异常。

public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable)

如果没有引发异常,或者引发了其他类型的异常,则此方法将失败。

请注意,允许使用相同类型的异常。 例如,如果您期望NumberFormatException并且抛出IllegalArgumentException,那么该测试也会通过,因为NumberFormatException扩展了IllegalArgumentException类。

2. JUnit5 预期异常示例

一个非常简单的示例可以是:

@Test
void testExpectedException() {

  Assertions.assertThrows(NumberFormatException.class, () -> {
    Integer.parseInt("One");
  });

}

如果参数不是有效数字,则此处的可执行代码为Integer.parseInt("One")引发NumberFormatException。 在上述代码中,"One"不是有效数字,因此代码将引发assertThrows()方法所期望的异常 - 因此测试通过。

3. 预期异常类的超类型

如前所述,您也可以期望异常类的超类型。 例如,上面的测试也可以用IllegalArgumentException编写。

@Test
void testExpectedExceptionWithSuperType() {

  Assertions.assertThrows(IllegalArgumentException.class, () -> {
    Integer.parseInt("One");
  });

}

该测试用例也将通过。

4. 其他预期异常类

如果可执行代码抛出任何其他异常类型,则测试将失败;否则,测试将失败。 甚至如果测试没有引发任何异常,那么测试也会失败。

例如,在下面的示例中,"1"是有效数字,因此不会引发异常。

@Test
void testExpectedExceptionFail() {

  Assertions.assertThrows(IllegalArgumentException.class, () -> {
    Integer.parseInt("1");
  });

}

5. 完整的例子

这是 JUnit5 断言异常消息的完整代码。

package com.howtodoinjava.junit5.examples.module;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class AppTest {

  @Test
  void testExpectedException() {

    Assertions.assertThrows(NumberFormatException.class, () -> {
      Integer.parseInt("One");
    });
  }

  @Test
  void testExpectedExceptionWithSuperType() {

    Assertions.assertThrows(IllegalArgumentException.class, () -> {
      Integer.parseInt("One");
    });
  }

  @Test
  void testExpectedExceptionFail() {

    Assertions.assertThrows(IllegalArgumentException.class, () -> {
      Integer.parseInt("1");
    });
  }

}

现在,使用测试套件执行此类。 您可以在 maven 示例中查看完整的项目配置。

package com.howtodoinjava.junit5.examples;

import org.junit.platform.runner.JUnitPlatform;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
public class JUnit5Example 
{
}

Eclipse 测试结果如下图所示:

JUnit5 Expected Exception Tests

JUnit5 预期异常测试

这就是 JUnit5 异常测试的全部。 将我的问题放在评论部分。

学习愉快!

源码下载

JUnit5 断言示例

原文: https://howtodoinjava.com/junit5/junit-5-assertions-examples/

JUnit5 断言帮助用测试用例的实际输出来验证期望的输出。 为简单起见,所有 JUnit Jupiter 断言org.junit.jupiter.Assertions类中的静态方法。

Table of Contents

Assertions.assertEquals() and Assertions.assertNotEquals()
Assertions.assertArrayEquals()
Assertions.assertIterableEquals()
Assertions.assertLinesMatch()
Assertions.assertNotNull() and Assertions.assertNull()
Assertions.assertNotSame() and Assertions.assertSame()
Assertions.assertTimeout() and Assertions.assertTimeoutPreemptively()
Assertions.assertTrue() and Assertions.assertFalse()
Assertions.assertThrows()
Assertions.fail()

Assertions.assertEquals()Assertions.assertNotEquals()示例

使用Assertions.assertEquals()断言期望值等于实际值assertEquals()针对不同的数据类型(例如, intshortfloatchar等。它还支持传递传递的错误消息,以防万一测试失败。 例如:

public static void assertEquals(int expected, int actual)
public static void assertEquals(int expected, int actual, String message)
public static void assertEquals(int expected, int actual, Supplier<String< messageSupplier)
void testCase() 
{
	//Test will pass
	Assertions.assertEquals(4, Calculator.add(2, 2));

	//Test will fail 
	Assertions.assertEquals(3, Calculator.add(2, 2), "Calculator.add(2, 2) test failed");

	//Test will fail 
	Supplier&lt;String&gt; messageSupplier  = ()-> "Calculator.add(2, 2) test failed";
	Assertions.assertEquals(3, Calculator.add(2, 2), messageSupplier);
}

类似地,Assertions.assertNotEquals()方法用于断言期望值不等于实际值。 与assertEquals()相比,assertNotEquals()不会针对不同的数据类型重载方法,而仅接受Object

public static void assertNotEquals(Object expected, Object actual)
public static void assertNotEquals(Object expected, Object actual, String message)
public static void assertNotEquals(Object expected, Object actual, Supplier<String> messageSupplier)
void testCase() 
{
	//Test will pass
	Assertions.assertNotEquals(3, Calculator.add(2, 2));

	//Test will fail 
	Assertions.assertNotEquals(4, Calculator.add(2, 2), "Calculator.add(2, 2) test failed");

	//Test will fail 
	Supplier&lt;String&gt; messageSupplier  = ()-> "Calculator.add(2, 2) test failed";
	Assertions.assertNotEquals(4, Calculator.add(2, 2), messageSupplier);
}

Assertions.assertArrayEquals()示例

assertEquals()相似,assertArrayEquals()对数组执行相同的操作,即断言期望数组等于实际数组。 它还具有针对不同数据类型的重载方法,例如boolean[]char[]int[]等。它还支持在测试失败的情况下传递要打印的错误消息。 例如:

public static void assertArrayEquals(int[] expected, int[] actual)
public static void assertArrayEquals(int[] expected, int[] actual, String message)
public static void assertArrayEquals(int[] expected, int[] actual, Supplier<String> messageSupplier)
void testCase() 
{
	//Test will pass
	Assertions.assertArrayEquals(new int[]{1,2,3}, new int[]{1,2,3}, "Array Equal Test");

	//Test will fail because element order is different
	Assertions.assertArrayEquals(new int[]{1,2,3}, new int[]{1,3,2}, "Array Equal Test");

	//Test will fail because number of elements are different
	Assertions.assertArrayEquals(new int[]{1,2,3}, new int[]{1,2,3,4}, "Array Equal Test");
}

Assertions.assertIterableEquals()示例

它断言期望和实际的可迭代项高度相等。 高度相等意味着集合中元素的数量和顺序必须相同; 以及迭代元素必须相等。

它还有 3 种重载方法。

public static void assertIterableEquals(Iterable<?> expected, Iterable> actual)
public static void assertIterableEquals(Iterable<?> expected, Iterable> actual, String message)
public static void assertIterableEquals(Iterable<?> expected, Iterable> actual, Supplier<String> messageSupplier)
@Test
void testCase() 
{
	 Iterable<Integer> listOne = new ArrayList<>(Arrays.asList(1,2,3,4));
	 Iterable<Integer> listTwo = new ArrayList<>(Arrays.asList(1,2,3,4));
	 Iterable<Integer> listThree = new ArrayList<>(Arrays.asList(1,2,3));
	 Iterable<Integer> listFour = new ArrayList<>(Arrays.asList(1,2,4,3));

	//Test will pass
	Assertions.assertIterableEquals(listOne, listTwo);

	//Test will fail
	Assertions.assertIterableEquals(listOne, listThree);

	//Test will fail
	Assertions.assertIterableEquals(listOne, listFour);
}

Assertions.assertLinesMatch()示例

它断言期望的字符串列表与实际列表相匹配。 将一个字符串与另一个字符串匹配的逻辑是:

  1. 检查expected.equals(actual) –如果是,则继续下一对
  2. 否则将expected视为正则表达式,并通过
    String.matches(String) 检查–如果是,则继续下一对
  3. 否则检查expected行是否为快进标记,如果是,则相应地应用
    快速前行并转到 1。

有效的快进标记是以>>开头和结尾并且至少包含 4 个字符的字符串。 快进文字之间的任何字符都将被丢弃。

>>>>
>> stacktrace >>
>> single line, non Integer.parse()-able comment >>

Assertions.assertNotNull()Assertions.assertNull()示例

assertNotNull()断言实际值不为空。 类似地,assertNull()方法断言实际值为空。 两者都有三种重载方法。

public static void assertNotNull(Object actual)
public static void assertNotNull(Object actual, String message)
public static void assertNotNull(Object actual, Supplier<String> messageSupplier)

public static void assertEquals(Object actual)
public static void assertEquals(Object actual, String message)
public static void assertEquals(Object actual, Supplier<String> messageSupplier)
@Test
void testCase() 
{	 
	String nullString = null;
	String notNullString = "howtodoinjava.com";

	//Test will pass
	Assertions.assertNotNull(notNullString);

	//Test will fail
	Assertions.assertNotNull(nullString);

	//Test will pass
	Assertions.assertNull(nullString);

	// Test will fail
	Assertions.assertNull(notNullString);
}

Assertions.assertNotSame()Assertions.assertSame()示例

assertNotSame()断言预期和实际不引用同一对象。 同样,assertSame()方法断言,预期和实际引用完全相同的对象。 两者都有三种重载方法。

public static void assertNotSame(Object actual)
public static void assertNotSame(Object actual, String message)
public static void assertNotSame(Object actual, Supplier<> messageSupplier)

public static void assertSame(Object actual)
public static void assertSame(Object actual, String message)
public static void assertSame(Object actual, Supplier<String> messageSupplier)
@Test
void testCase() 
{	 
	String originalObject = "howtodoinjava.com";
	String cloneObject = originalObject;
	String otherObject = "example.com";

	//Test will pass
	Assertions.assertNotSame(originalObject, otherObject);

	//Test will fail
	Assertions.assertNotSame(originalObject, cloneObject);

	//Test will pass
	Assertions.assertSame(originalObject, cloneObject);

	// Test will fail
	Assertions.assertSame(originalObject, otherObject);
}

Assertions.assertTimeout()Assertions.assertTimeoutPreemptively()示例

assertTimeout()assertTimeoutPreemptively()均用于测试长时间运行的任务。 如果测试用例中的给定任务花费的时间超过指定的持续时间,则测试将失败。

两种方法之间唯一的区别是assertTimeoutPreemptively()中的设置,如果超过超时,ExecutableThrowingSupplier的执行将被抢先中止。 在assertTimeout()的情况下,不会中断ExecutableThrowingSupplier

public static void assertTimeout(Duration timeout, Executable executable)
public static void assertTimeout(Duration timeout, Executable executable, String message)
public static void assertTimeout(Duration timeout, Executable executable, Supplier<String> messageSupplier)
public static void assertTimeout(Duration timeout, ThrowingSupplier<T> supplier, String message)
public static void assertTimeout(Duration timeout, ThrowingSupplier<T> supplier, Supplier<String> messageSupplier)
@Test
void testCase() {

	//This will pass
	Assertions.assertTimeout(Duration.ofMinutes(1), () -> {
		return "result";
	});

	//This will fail
	Assertions.assertTimeout(Duration.ofMillis(100), () -> {
		Thread.sleep(200);
		return "result";
	});

	//This will fail
	Assertions.assertTimeoutPreemptively(Duration.ofMillis(100), () -> {
		Thread.sleep(200);
		return "result";
	});
}

Assertions.assertTrue()Assertions.assertFalse()示例

assertTrue()断言BooleanSupplier提供的条件为真。 类似地,assertFalse()断言提供的条件为假。 它具有以下重载方法:

public static void assertTrue(boolean condition)
public static void assertTrue(boolean condition, String message)
public static void assertTrue(boolean condition, Supplier<String> messageSupplier)
public static void assertTrue(BooleanSupplier booleanSupplier)
public static void assertTrue(BooleanSupplier booleanSupplier, String message)
public static void assertTrue(BooleanSupplier booleanSupplier, Supplier<String> messageSupplier)

public static void assertFalse(boolean condition)
public static void assertFalse(boolean condition, String message)
public static void assertFalse(boolean condition, Supplier<String> messageSupplier)
public static void assertFalse(BooleanSupplier booleanSupplier)
public static void assertFalse(BooleanSupplier booleanSupplier, String message)
public static void assertFalse(BooleanSupplier booleanSupplier, Supplier<String> messageSupplier)
@Test
void testCase() {

	boolean trueBool = true;
	boolean falseBool = false;

	Assertions.assertTrue(trueBool);
	Assertions.assertTrue(falseBool, "test execution message");
	Assertions.assertTrue(falseBool, AppTest::message);
	Assertions.assertTrue(AppTest::getResult, AppTest::message);

	Assertions.assertFalse(falseBool);
	Assertions.assertFalse(trueBool, "test execution message");
	Assertions.assertFalse(trueBool, AppTest::message);
	Assertions.assertFalse(AppTest::getResult, AppTest::message);
}

private static String message () {
	return "Test execution result";
}

private static boolean getResult () {
	return true;
}

Assertions.assertThrows()示例

它断言所提供的Executable的执行将引发expectedType的异常并返回该异常。

public static <T extends Throwable> T assertThrows(Class<T> expectedType, 
	Executable executable)
@Test
void testCase() {

	Throwable exception = Assertions.assertThrows(IllegalArgumentException.class, () -> {
        throw new IllegalArgumentException("error message");
    });
}

Assertions.fail()示例

fail()方法仅使测试失败。 它具有以下重载方法:

public static void fail(String message)
public static void fail(Throwable cause)
public static void fail(String message, Throwable cause)
public static void fail(Supplier<String> messageSupplier)
public class AppTest {
	@Test
	void testCase() {

		Assertions.fail("not found good reason to pass");
		Assertions.fail(AppTest::message);
	}

	private static String message () {
		return "not found good reason to pass";
	}
}

将我的问题放在评论部分。

学习愉快!

Eclipse 中的 Maven 多模块项目

原文: https://howtodoinjava.com/maven/multi-module-project-eclipse/

了解如何在 Eclipse IDE 中创建多模块 Maven 项目。 在此 maven 教程中,我们将学习在 eclipse 中创建嵌套的 maven 项目

1. 创建多模块 Maven 项目

让我们创建一个 maven 项目,其中包含 earwarjar 类型的模块。 我们正在创建企业应用的结构,其中应用将作为 EAR(企业应用获取)文件部署到应用服务器(例如 weblogic,websphere)。

该 EAR 将包含一个(或多个)WAR(Web 应用资源)文件,并且每个 war 将包含服务项目,该服务项目具有面向所有 war 文件的通用代码,打包类型为 JAR(Java ARchive)。

Projects Relationship

项目关系

1.1 创建父项目 - 打包类型为pom

在 Eclipse 中创建一个新的 Maven 项目。 将其包类型设置为pom

Create new maven project

创建 maven 项目

Fill maven group id and artifact id

完整的 maven 组 id 和工件 id

Change packaging jar to pom

将打包 jar 修改为 pom

1.2 创建 Maven 模块 - 包类型为EAR

在父项目中创建一个新的 Maven 模块。 将其包类型更改为'ear'。 该模块可以具有 maven EAR 插件,该插件最终将构建要在服务器上部署的 EAR 文件。

Create new maven module

创建新的 maven 模块

Add Module Name

添加模块名称

Fill archetype details

完成的原型详细信息

1.3 创建 Maven 模块 - 打包类型为warjar

与 ear 模块类似,为 war 文件和 jar 服务文件再创建两个模块。 分别更改其包并添加 maven 插件。

1.4 最后结果

现在,在 eclipse 中观察最终的项目结构,并验证所有项目的pom.xml文件。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
        http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.howtodoinjava.app</groupId>
	<artifactId>HelloWorldApp</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>pom</packaging>

	<name>HelloWorldApp</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>

	<modules>
		<module>web</module>
		<module>service</module>
		<module>ear</module>
	</modules>
</project>

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.howtodoinjava.app</groupId>
		<artifactId>HelloWorldApp</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>ear</artifactId>
	<name>ear</name>
	<packaging>ear</packaging>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>
	<dependencies>
		<dependency>
			<groupId>com.howtodoinjava.app</groupId>
			<artifactId>web</artifactId>
			<version>0.0.1-SNAPSHOT</version>
			<type>war</type>
		</dependency>
	</dependencies>
	<build>
		<pluginManagement>
		<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-ear-plugin</artifactId>
			<version>3.0.1</version>
			<configuration>
				<modules>
					<webModule>
						<groupId>com.howtodoinjava.app</groupId>
						<artifactId>web</artifactId>
						<uri>web-0.0.1-SNAPSHOT.war</uri>
						<!-- Set custom context root -->
						<contextRoot>/application</contextRoot>
					</webModule>
				</modules>
			</configuration>
		</plugin>
		</plugins>
		</pluginManagement>
	</build>
</project>

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.howtodoinjava.app</groupId>
		<artifactId>HelloWorldApp</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<groupId>com.howtodoinjava.app</groupId>
	<artifactId>web</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>web</name>
	<url>http://maven.apache.org</url>
	<packaging>war</packaging>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>
	<dependencies>
		<dependency>
			<groupId>com.howtodoinjava.app</groupId>
			<artifactId>service</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
	</dependencies>
</project>

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.howtodoinjava.app</groupId>
		<artifactId>HelloWorldApp</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<groupId>com.howtodoinjava.app</groupId>
	<artifactId>service</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>service</name>
	<url>http://maven.apache.org</url>
	<packaging>jar</packaging>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

要生成项目,请从控制台运行$mvn clean install命令。

E:\devsetup\workspacetemp\HelloWorldApp>mvn clean install

[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] HelloWorldApp                                                      [pom]
[INFO] service                                                            [jar]
[INFO] web                                                                [war]
[INFO] ear                                                                [ear]
[INFO]
...
...
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ service ---
[WARNING] JAR will be empty - no content was marked for inclusion!
[INFO] Building jar: E:\devsetup\workspacetemp\HelloWorldApp\service\target\service-0.0.1-SNAPSHOT.jar
[INFO]
...
...
[INFO] Packaging webapp
[INFO] Assembling webapp [web] in [E:\devsetup\workspacetemp\HelloWorldApp\web\target\web-0.0.1-SNAPSHOT]
[INFO] Processing war project
[INFO] Copying webapp resources [E:\devsetup\workspacetemp\HelloWorldApp\web\src\main\webapp]
[INFO] Webapp assembled in [47 msecs]
[INFO] Building war: E:\devsetup\workspacetemp\HelloWorldApp\web\target\web-0.0.1-SNAPSHOT.war
[INFO] WEB-INF\web.xml already added, skipping
[INFO]
...
...
[INFO]
[INFO] --- maven-ear-plugin:3.0.1:ear (default-ear) @ ear ---
[INFO] Copying artifact [war:com.howtodoinjava.app:web:0.0.1-SNAPSHOT] to [web-0.0.1-SNAPSHOT.war]
[INFO] Copy ear sources to E:\devsetup\workspacetemp\HelloWorldApp\ear\target\ear-0.0.1-SNAPSHOT
[INFO] Building jar: E:\devsetup\workspacetemp\HelloWorldApp\ear\target\ear-0.0.1-SNAPSHOT.ear
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] HelloWorldApp 0.0.1-SNAPSHOT ....................... SUCCESS [  0.328 s]
[INFO] service ............................................ SUCCESS [  0.839 s]
[INFO] web ................................................ SUCCESS [  0.838 s]
[INFO] ear 0.0.1-SNAPSHOT ................................. SUCCESS [  0.588 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.749 s
[INFO] Finished at: 2018-11-18T15:04:52+05:30
[INFO] ------------------------------------------------------------------------

构建会生成名称为ear-0.0.1-SNAPSHOT.ear的 ear 文件。 可以根据需要随意更改项目名称和生成的包。

2. 常见问题

2.1 groupId与父groupId重复

Maven 子项目从父项目继承属性。 如果任何 Maven 模块/项目的组 ID 或版本 ID 与父项目相同,那么我们只需删除此条目即可。

Remove duplicate entries

移除重复项

2.2 工件不是项目的依赖项

如果在未指定类型属性的情况下添加模块依赖项(war),我们可能会遇到此错误。

Module type attribute

模块类型属性

向我提供与在 Eclipse IDE 中创建多模块 Maven 项目有关的问题。

学习愉快!

JUnit5 假设示例

原文: https://howtodoinjava.com/junit5/junit-5-assumptions-examples/

JUnit5 Assumptions类提供静态方法来支持基于假设的条件测试执行。 假设失败会导致测试中止。 假设通常在继续执行给定测试方法没有意义的情况下使用。 在测试报告中,这些测试将被标记为通过。

JUnit jupiter Assumptions类具有两个这样的方法:assumeFalse()assumeTrue()

第三个方法assumeThat()处于Experimental状态并且可能在未来得到确认。

Table of Contents

Assumptions.assumeTrue()
Assumptions.assumeFalse()

JUnit5 的Assumptions.assumeTrue()

assumeTrue()验证给定的假设为true,如果假设为true,则进行测试,否则测试执行将中止。

它具有以下重载方法。

public static void assumeTrue(boolean assumption) throws TestAbortedException
public static void assumeTrue(boolean assumption, Supplier<String> messageSupplier) throws TestAbortedException
public static void assumeTrue(boolean assumption, String message) throws TestAbortedException

public static void assumeTrue(BooleanSupplier assumptionSupplier) throws TestAbortedException
public static void assumeTrue(BooleanSupplier assumptionSupplier, String message) throws TestAbortedException
public static void assumeTrue(BooleanSupplier assumptionSupplier, Supplier<String> messageSupplier) throws TestAbortedException
public class AppTest {
	@Test
    void testOnDev() 
	{
		System.setProperty("ENV", "DEV");
        Assumptions.assumeTrue("DEV".equals(System.getProperty("ENV")));
        //remainder of test will proceed
    }

	@Test
    void testOnProd() 
	{
		System.setProperty("ENV", "PROD");
        Assumptions.assumeTrue("DEV".equals(System.getProperty("ENV")), AppTest::message);
        //remainder of test will be aborted
    }

	private static String message () {
		return "TEST Execution Failed :: ";
	}
}

JUnit5 的Assumptions.assumeFalse()

assumeFalse()会验证给定的假设是否为假,如果假设为假,则执行测试,否则测试执行将中止。 它的工作与assumeTrue()相反。

它具有以下重载方法。

public static void assumeFalse(boolean assumption) throws TestAbortedException
public static void assumeFalse(boolean assumption, Supplier<String> messageSupplier) throws TestAbortedException
public static void assumeFalse(boolean assumption, String message) throws TestAbortedException

public static void assumeFalse(BooleanSupplier assumptionSupplier) throws TestAbortedException
public static void assumeFalse(BooleanSupplier assumptionSupplier, String message) throws TestAbortedException
public static void assumeFalse(BooleanSupplier assumptionSupplier, Supplier<String> messageSupplier) throws TestAbortedException
public class AppTest {
	@Test
    void testOnDev() 
	{
		System.setProperty("ENV", "DEV");
        Assumptions.assumeFalse("DEV".equals(System.getProperty("ENV")), AppTest::message);
      //remainder of test will be aborted
    }

	@Test
    void testOnProd() 
	{
		System.setProperty("ENV", "PROD");
        Assumptions.assumeFalse("DEV".equals(System.getProperty("ENV")));
      //remainder of test will proceed

    }

	private static String message () {
		return "TEST Execution Failed :: ";
	}
}

将我的问题放在评论部分。

学习愉快!

JUnit5 测试套件示例

原文: https://howtodoinjava.com/junit5/junit5-test-suites-examples/

使用 JUnit5 测试套件,您可以运行分散到多个测试类和不同包中的测试。 JUnit5 提供了两个注解:@SelectPackages@SelectClasses以创建测试套件。 此外,您可以使用其他注解来过滤测试包,类甚至测试方法。

Table of Contents

Project Structure for Test classes and Suite
Create Test Suite with JUnit5 @SelectPackages
Create Test Suite with JUnit5 @SelectClasses
Filtering Packages with @IncludePackages and @ExcludePackages
Filtering Test Classes with @IncludeClassNamePatterns and @ExcludeClassNamePatterns
Filtering Tests with @IncludeTags and @ExcludeTags

测试类名称必须遵循正则表达式模式^.*Tests?$。 这意味着测试类名称必须以TestTests结尾。 例如UserMgmtTestsDeviceMgmtTest等。

1. 测试类和套件的项目结构

对于此示例,我使用以下项目结构。

JUnit5 Test Suite Project Structure

JUnit5 测试套件项目结构

2. 使用@SelectPackages创建测试套件

@SelectPackages指定通过@RunWith(JUnitPlatform.class)运行测试套件时要选择的包的名称。

指定单个包

packageName作为参数传递给@SelectPackages注解。

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples.packageA") 
public class JUnit5TestSuiteExample 
{
}

@SelectPackages - Single Package Example

@SelectPackages – 单个包示例

指定多个包

将参数中的程序包名称作为字符串数组(在大括号{}中)传递给@SelectPackages注解。

@RunWith(JUnitPlatform.class)
@SelectPackages({"com.howtodoinjava.junit5.examples.packageA","com.howtodoinjava.junit5.examples.packageB"}) 
public class JUnit5TestSuiteExample 
{
}

@SelectPackages - Multiple Packages Example

@SelectPackages – 多个包示例

请注意,如果我们在@SelectPackages注解中传递packageX,则将“此软件包及其所有子软件包中的测试类”用于测试套件。

3. 使用@SelectClasses创建测试套件

@SelectClasses指定通过@RunWith(JUnitPlatform.class)运行测试套件时要选择的类。

指定单个类

ClassName.class作为参数传递到@SelectClasses注解。

@RunWith(JUnitPlatform.class)
@SelectClasses( ClassATest.class )
public class JUnit5TestSuiteExample 
{
}

@SelectClasses - Single Class Example

@SelectClasses – 单个类示例

指定多个类

将参数中的类名称作为数组(在大括号{}中)传递给@SelectClasses注解。

@RunWith(JUnitPlatform.class)
@SelectClasses( { ClassATest.class, ClassBTest.class, ClassCTest.class } )
public class JUnit5TestSuiteExample 
{
}

@SelectClasses - Multiple Test Classes Example

@SelectClasses – 多个测试类示例

4. @IncludePackages@ExcludePackages

我们了解到@SelectPackages也会导致其所有子包都被扫描以查找测试类。 如果要排除任何特定的子包,或包括任何包,则可以使用 @IncludePackages@ExcludePackages注解。

@IncludePackages示例

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
@IncludePackages("com.howtodoinjava.junit5.examples.packageC")
public class JUnit5TestSuiteExample 
{
}

这只会添加com.howtodoinjava.junit5.examples.packageC中的测试类(即ClassCTest)中的测试。

@ExcludePackages示例

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
@ExcludePackages("com.howtodoinjava.junit5.examples.packageC")
public class JUnit5TestSuiteExample 
{
}

这将添加com.howtodoinjava.junit5.examples中测试类的测试,但不包括子包com.howtodoinjava.junit5.examples.packageC(即ClassATestClassBTest)中的所有测试类。

5. @IncludeClassNamePatterns@ExcludeClassNamePatterns

很多时候,在选择注解中包含所有包或测试类名称是不可行的。 在这种情况下,您可能会提供更广泛的包范围,并通过 appy 筛选将哪些测试类包括在套件中或从套件中排除。

要指定要排除或包含的测试类名称模式,可以使用@IncludeClassNamePatterns@ExcludeClassNamePatterns注解。

@IncludeClassNamePatterns示例

包括名称以ATestATests结尾的所有测试类。

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
@IncludeClassNamePatterns({"^.*ATests?$"})
public class JUnit5TestSuiteExample 
{
}

@ExcludeClassNamePatterns示例

排除名称以ATestATests结尾的所有测试类。

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
@ExcludeClassNamePatterns({"^.*ATests?$"})
public class JUnit5TestSuiteExample 
{
}

您可以在上述注解中应用多个模式。 在多种模式的情况下,它们使用“OR”语义进行组合。 这意味着,如果类别的完全限定名称与至少一种模式匹配,则该类别将包含在测试套件中/从测试套件中排除。

6. @IncludeTags@ExcludeTags

在企业应用中,您可能已经标记了要在特定环境中运行的测试用例,例如开发或生产。 您还可以在测试套件中包含或排除基于这些标记的测试。

@IncludeTags示例

此测试套件将运行包com.howtodoinjava.junit5.examples(及其子包)中标记有production的所有测试。

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
@IncludeTags("production")
public class JUnit5TestSuiteExample 
{
}

@ExcludeTags示例

此测试套件将排除包com.howtodoinjava.junit5.examples(及其子包)中所有带有development标签的测试。

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
@ExcludeTags("development")
public class JUnit5TestSuiteExample 
{
}

显然,有多种方法在 JUnit5 中创建测试套件,并且它强烈支持过滤测试套件中的测试。

将我的问题放在评论部分。

学习愉快!

源码下载

JUnit5 和 Gradle

原文: https://howtodoinjava.com/junit5/junit-5-gradle-dependency-build-gradle-example/

了解使用 gradle 配置它的不同模块,以及如何使用它们来创建和执行测试。

请注意,JUnit5 在运行时需要 Java8。

1. JUnit5 Gradle 依赖项

要通过 gradle 运行 JUnit5 测试,您将至少需要两个依赖项。

  1. JUnit Jupiter 引擎依赖项

    JUnit jupiter 必须具有两个依赖项,即junit-jupiter-apijunit-jupiter-enginejunit-jupiter-api具有 junit 注解(例如@Test)以编写测试和扩展名,junit-jupiter-engine具有测试引擎实现,在运行时需要执行该引擎才能执行测试。

    在内部,junit-jupiter-engine依赖于junit-jupiter-api,因此添加junit-jupiter-engine仅将两个依赖项都带入类路径。

    我们可以在此图像中了解各种 junit jar 之间的内部依赖项

    dependencies {
    	testRuntime("org.junit.jupiter:junit-jupiter-engine:5.5.2")
            testRuntime("org.junit.platform:junit-platform-runner:1.5.2")
    }
    test {
        useJUnitPlatform()
    }
    
    
  2. JUnit 平台运行器依赖项

    我们需要junit-platform-runner用于在 JUnit4 环境中的 JUnit 平台上执行测试和测试套件。

    在内部,junit-platform-runner依赖于junit-platform-suite-apijunit-platform-launcher,因此添加junit-jupiter-engine仅将所有三个依赖项引入类路径中。

    testRuntime("org.junit.platform:junit-platform-runner:1.5.2")
    

2. 使用 JUnit5 执行 JUnit4 测试

要在 JUnit5 环境中执行 JUnit4 测试,您将需要包括junit-vintage-engine依赖项。 JUnit Vintage 提供了TestEngine,用于在平台上运行基于 JUnit 3 和 JUnit4 的测试。

dependencies {
    //To run JUnit 3 and JUnit4 tests
    testCompile("junit:junit:4.12")
    testRuntime("org.junit.vintage:junit-vintage-engine:4.12.0-M4")
}

通过在build.gradle中进行上述配置,现在您可以使用 JUnit5 运行旧的 junit 3 或 JUnit4 测试。

3. JUnit5 Gradle 示例

用于运行用 JUnit5 构建的测试的示例build.gradle文件如下:

buildscript {
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.2'
	}
}

repositories {
	mavenCentral()
}

ext.junit4Version        = '4.12'
ext.junitVintageVersion  = '4.12.2'
ext.junitPlatformVersion = '1.0.2'
ext.junitJupiterVersion  = '5.0.2'
ext.log4jVersion         = '2.9.0'

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.junit.platform.gradle.plugin'

jar {
	baseName = 'junit5-gradle-consumer'
	version = '1.0.0-SNAPSHOT'
}

compileTestJava {
	sourceCompatibility = 1.8
	targetCompatibility = 1.8
	options.compilerArgs += '-parameters'
}

junitPlatform {
	// platformVersion '1.0.2'
	filters {
		engines {
			// include 'junit-jupiter', 'junit-vintage'
			// exclude 'custom-engine'
		}
		tags {
			// include 'fast'
			exclude 'slow'
		}
		// includeClassNamePattern '.*Test'
	}
	// configurationParameter 'junit.jupiter.conditions.deactivate', '*'
	// enableStandardTestTask true
	// reportsDir file('build/test-results/junit-platform') // this is the default
	logManager 'org.apache.logging.log4j.jul.LogManager'
}

dependencies {
	// JUnit Jupiter API and TestEngine implementation
	testCompile("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
	testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")

	// If you also want to support JUnit 3 and JUnit4 tests
	testCompile("junit:junit:${junit4Version}")
	testRuntime("org.junit.vintage:junit-vintage-engine:${junitVintageVersion}")

	// To avoid compiler warnings about @API annotations in JUnit code
	testCompileOnly('org.apiguardian:apiguardian-api:1.0.0')

	// To use Log4J's LogManager
	testRuntime("org.apache.logging.log4j:log4j-core:${log4jVersion}")
	testRuntime("org.apache.logging.log4j:log4j-jul:${log4jVersion}")

	// Only needed to run tests in an (IntelliJ) IDE(A) that bundles an older version
	testRuntime("org.junit.platform:junit-platform-launcher:${junitPlatformVersion}")
}

task wrapper(type: Wrapper) {
	description = 'Generates gradlew[.bat] scripts'
	gradleVersion = '4.3.1'
}

参考: Git

将我的问题放在评论部分。

学习愉快!

JUnit5 Maven 依赖项

原文: https://howtodoinjava.com/junit5/junit5-maven-dependency/

了解使用 Maven 配置其 JUnit5 的不同模块,以及如何使用它们创建和执行测试。

请注意,JUnit5 在运行时需要 Java8。

1. JUnit5 Maven 依赖项

要通过 maven 运行 JUnit5 测试,您将至少需要两个依赖项。

  1. JUnit Jupiter 引擎依赖项

    JUnit Jupiter 需要具有两个依赖项,即junit-jupiter-apijunit-jupiter-enginejunit-jupiter-api具有 junit 注解(例如@Test)以编写测试和扩展名,junit-jupiter-engine具有测试引擎实现,在运行时需要执行该引擎才能执行测试。

    在内部,junit-jupiter-engine依赖于junit-jupiter-api,因此添加junit-jupiter-engine仅将两个依赖项都带入类路径。

    您可以在此图像中了解各种 junit jar 之间的内部依赖项

    <dependency>
    	<groupId>org.junit.jupiter</groupId>
    	<artifactId>junit-jupiter-engine</artifactId>
    	<version>5.5.2</version>
    </dependency>
    
    

    我们来看看依赖树:

    JUnit5 jupiter engine dependency tree

    JUnit5 jupiter 引擎依赖树

  2. JUnit 平台运行器依赖项

    在 JUnit4 环境中,需要junit-platform-runner用于在 JUnit 平台上执行测试和测试套件

    在内部,junit-platform-runner依赖于junit-platform-suite-apijunit-platform-launcher,因此添加junit-jupiter-engine仅将所有三个依赖项引入类路径中。

    <dependency>
    	<groupId>org.junit.platform</groupId>
    	<artifactId>junit-platform-runner</artifactId>
    	<version>${junit.platform.version}</version>
    	<scope>test</scope>
    </dependency>
    
    

    我们来看看依赖树:

    JUnit5 platform runner dependency tree

    JUnit5 平台运行器依赖树

2. 使用 JUnit5 执行 JUnit4 测试

要在 JUnit5 环境中执行 JUnit4 测试,您将需要JUnit Platform Surefire Provider插件。 只要您在 JUnit4 上配置test依赖项并将 JUnit Vintage TestEngine 实现添加到maven-surefire-plugin的依赖项中,它就可以运行基于 JUnit4 的测试,如下所示。

<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.19</version>
            <dependencies>
                <dependency>
                    <groupId>org.junit.platform</groupId>
                    <artifactId>junit-platform-surefire-provider</artifactId>
                    <version>1.0.0-M4</version>
                </dependency>
                <dependency>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                    <version>4.12.0-M4</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

通过在pom.xml中进行上述配置,现在您可以使用 JUnit5 运行旧的测试。

3. JUnit5 Maven 示例

用于运行用 JUnit5 构建的测试的示例pom.xml文件如下:

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
        http://maven.apache.org/xsd/maven-4.0.0.xsd">

	<modelVersion>4.0.0</modelVersion>
	<groupId>com.howtodoinjava</groupId>
	<artifactId>JUnit5Examples</artifactId>

	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
		<junit.jupiter.version>5.5.2</junit.jupiter.version>
		<junit.platform.version>1.5.2</junit.platform.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-engine</artifactId>
			<version>${junit.jupiter.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.junit.platform</groupId>
			<artifactId>junit-platform-runner</artifactId>
			<version>${junit.platform.version}</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
			</plugin>
			<plugin>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>2.22.2</version>
			</plugin>
		</plugins>
	</build>
</project>

将我的问题放在评论部分。

学习愉快!

源码下载

JUnit5 – 在 Eclipse 中执行测试

原文: https://howtodoinjava.com/junit5/execute-testcase-eclipse/

学习在 Eclipse IDE 中执行 JUnit5 测试。 在这个 JUnit5 示例中,已使用 Maven 导入依赖项。

1. JUnit5 Maven 依赖项

为了能够在 Eclipse 中执行 JUnit5 测试,我们需要以下依赖项。

  • test范围中的junit-platform-runnerJUnitPlatform运行器的位置
  • test范围中的junit-jupiter-api:用于编写测试的 API,包括@Test等。当包含junit-jupiter-engine时,将其包括在内。
  • 测试运行时范围中的junit-jupiter-engine:JUnit Jupiter 的引擎 API 的实现。
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.howtodoinjava</groupId>
	<artifactId>JUnit5Examples</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
		<junit.jupiter.version>5.5.2</junit.jupiter.version>
		<junit.platform.version>1.5.2</junit.platform.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-engine</artifactId>
			<version>${junit.jupiter.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.junit.platform</groupId>
			<artifactId>junit-platform-runner</artifactId>
			<version>${junit.platform.version}</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
			</plugin>
			<plugin>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>2.22.2</version>
			</plugin>
		</plugins>
	</build>
</project>

2. @RunWith(JUnitPlatform.class)

JUnitPlatform允许测试与 IDE 一起运行并构建支持 JUnit4 但尚不直接支持 JUnit 平台的系统。

package net.restfulapi.demo;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
public class TestApplication { 
	@Test
	@DisplayName("My First Test")
	void myFirstTest(TestInfo testInfo) {
		NumericCalculator calculator = new NumericCalculator();
		Assertions.assertEquals(2, calculator.add(1, 1), "1 + 1 = 2");
		Assertions.assertEquals("My First Test", testInfo.getDisplayName(), 
									() -> "TestInfo is injected correctly");
	}
}

其中NumericCalculator类是:

package net.restfulapi.demo;

public class NumericCalculator {
	public int add(int a, int b) {
		return a + b;
	}
}

3. 演示

现在,将其余部分作为 Eclipse 中的 junit 测试用例运行。

您将获得以下输出。

JUnit5 eclipse example

JUnit5 eclipse 示例

学习愉快!

Eclipse 的 JUnit5 测试模板

原文: https://howtodoinjava.com/junit5/code-test-templates-eclipse/

Eclipse 对 JUnit 测试用例提供了强大的工具支持。 在 Eclipse 中配置用于 JUnit 测试用例的代码模板是加快测试开发速度的重要功能。 在 Eclipse 中学习创建和导入 JUnit5 测试模板

JUnit5 测试模板

在以下模板文件中给出的配置三个 JUnit5 模板方法,即

  1. @BeforeEach注解的setUp()
  2. @AfterEach注解的tearDown()
  3. @Test注解的testXXX()

当您使用test()模板时,它将自动添加以下导入语句:

import static org.junit.jupiter.api.Assertions.*;

它可以直接使用所有静态assertXXX()方法进行测试。

<?xml version="1.0" encoding="UTF-8" standalone="no"?><templates><template autoinsert="true" context="java" deleted="false" description="JUnit5 BeforeEach" enabled="true" name="setup (JUnit5)">${:import(org.junit.jupiter.api.BeforeEach)}
@BeforeEach
public void setUp() {
  ${cursor}
}</template><template autoinsert="true" context="java" deleted="false" description="JUnit5 AfterEach" enabled="true" name="teardown (JUnit5)">${:import(org.junit.jupiter.api.AfterEach)}
@AfterEach
public void tearDown() {
  ${cursor}
}</template><template autoinsert="false" context="java-members" deleted="false" description="JUnit5 test method" enabled="true" id="org.eclipse.jdt.ui.templates.test" name="test (JUnit5)">${:import(org.junit.jupiter.api.Test)}
@Test
public void test${name}() {
	${staticImport:importStatic('org.junit.jupiter.api.Assertions.*')}
	${cursor} 
}</template></templates>

将此 XML 保存到您首选位置的文件中。 我们将在下一步中导入该文件。

将代码模板导入 Eclipse

  1. Eclipse:Window -> 首选项
  2. Java -> 编辑器 -> 模板
  3. 导入…
  4. 选择文件
  5. 验证特定于 JUnit5 的模板名称“setup(JUnit5)”,“teardown(JUnit5)”和“test(JUnit5)

JUnit5 Test Templates for Eclipse

JUnit5 Eclipse 的测试模板

如何使用测试模板

要使用模板,请将光标放在新行中,然后输入 2-3 个模板方法的缩写。 现在点击CTRL+ENTER。 它将以下面的方式打开一个弹出窗口。

Use JUnit5 Test Templates in Eclipse

使用 JUnit5 Eclipse 的测试模板

使用向上或向下箭头键选择模板方法,然后按Enter

它将生成放置光标的模板代码

将我的问题放在评论部分。

学习愉快!

JUnit5 与 JUnit4

原文: https://howtodoinjava.com/junit5/junit-5-vs-junit-4/

JUnit5 旨在适应 Java8 编码风格,并且比 JUnit4 更加健壮和灵活。在本篇文章 JUnit5 vs JUnit4 中,我们将重点介绍 JUnit4 和 JUnit5 之间的一些主要区别。

1. JUnit5 与 JUnit4 – 注解

两个版本中的大多数注解都相同,但几乎没有区别。 这是一个快速比较。

特性 JUnit4 JUnit5
声明测试方法 @Test @Test
在当前类中的所有测试方法之前执行 @BeforeClass @BeforeAll
在当前类中的所有测试方法之后执行 @AfterClass @AfterAll
在每种测试方法之前执行 @Before @BeforeEach
在每种测试方法之后执行 @After @AfterEach
禁用测试方法/类 @Ignore @Disabled
进行动态测试的测试工厂 不适用 @TestFactory
嵌套测试 NA @Nested
标记和过滤 @Category @Tag
注册自定义扩展 NA @ExtendWith

2. JUnit5 和 JUnit4 之间的其他区别

2.1 架构

JUnit4 将所有内容捆绑到单个 jar 文件中。

JUnit5 由 3 个子项目组成,即 JUnit Platform,JUnit Jupiter 和 JUnit Vintage。

  1. JUnit 平台

    它定义了TestEngine API,用于开发在平台上运行的新测试框架。

  2. JUnit Jupiter

    它具有所有新的 junit 注解和TestEngine实现,以运行使用这些注解编写的测试。

  3. JUnit Vintage

    为了支持在 JUnit5 平台上运行 JUnit 3 和 JUnit4 书面测试。

2.2 所需的 JDK 版本

JUnit4 需要 Java 5 或更高版本。

JUnit5 需要 Java8 或更高版本。

2.3 断言

在 JUnit4 中,org.junit.Assert具有所有肯定的方法来验证预期结果和结果。
他们接受错误消息的额外参数作为方法签名中的第一个参数。 例如:

public static void assertEquals(long expected, long actual)
public static void assertEquals(String message, long expected, long actual)

在 JUnit5 中,org.junit.jupiter.Assertions包含大多数断言方法,包括其他assertThrows()assertAll()方法。 到目前为止,assertAll()处于实验状态,用于分组断言。

JUnit5 断言方法还具有重载方法,以在测试失败(例如测试失败)时支持传递要打印的错误消息。

public static void assertEquals(long expected, long actual)
public static void assertEquals(long expected, long actual, String message)
public static void assertEquals(long expected, long actual, Supplier messageSupplier)

2.4 假设

在 JUnit4 中,org.junit.Assume包含一些方法,用于陈述有关测试有意义的条件的假设。 它具有以下五种方法:

  1. assumeFalse()
  2. assumeNoException()
  3. assumeNotNull()
  4. assumeThat()
  5. assumeTrue()

在 JUnit5 中,org.junit.jupiter.api.Assumptions 包含一些方法,用于陈述有关测试有意义的条件的假设。 它具有以下三种方法:

  1. assumeFalse()
  2. assumingThat​()
  3. assumeTrue()

2.5 标记和过滤

在 JUnit4 中,使用@category注解。

在 JUnit5 中,使用@tag注解。

2.6 测试套件

在 JUnit4 中,@RunWith@Suite注解。 例如:

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)
@Suite.SuiteClasses({
        ExceptionTest.class, 
        TimeoutTest.class
})
public class JUnit4Example 
{
}

在 JUnit5 中,@RunWith@SelectPackages@SelectClasses例如:

import org.junit.platform.runner.JUnitPlatform;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
@SelectPackages("com.howtodoinjava.junit5.examples")
public class JUnit5Example 
{
}

2.7 第三方整合

在 JUnit4 中,不支持对第三方插件和 IDE 的集成。 他们必须依靠反射

JUnit5 为此有专门的子项目,即 JUnit Platform。 它定义了TestEngine API,用于开发在平台上运行的测试框架。

请在评论部分中对 JUnit5 和 JUnit4 之间的比较发表您的看法。

学习愉快!

JUnit4 教程

JUnit 教程

原文: https://howtodoinjava.com/junit-4/

JUnit 是 Java 编程语言的单元测试框架。 JUnit 在测试驱动开发的开发中很重要,并且是一系列单元测试框架之一。 它的主要用途是为您的应用代码单元编写可重复的测试。

安装

要将 JUnit 包含到您的项目中,您需要将其依赖项包含在类路径中。

  • JUnit Maven 依赖项

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    
    
  • JUnit Gradle 依赖项

dependencies {
  testCompile 'junit:junit:4.12'
}

JUnit 注解

JUnit 提供以下注解来编写测试。

注解 描述
@Before 带注解的方法将在测试类中的每个测试方法之前运行。
@After 带注解的方法将在测试类中的每个测试方法之后运行。
@BeforeClass 带注解的方法将在测试类中的所有测试方法之前运行。 此方法必须是静态的。
@AfterClass 带注解的方法将在测试类中的所有测试方法之后运行。 此方法必须是静态的。
@Test 用于将方法标记为 junit 测试
@Ignore 它用于禁用或忽略测试套件中的测试类或方法。
@FixMethodOrder 此类允许用户选择测试类中方法的执行顺序。
@Rule 注解引用规则的字段或返回规则的方法。
@ClassRule 注解引用规则的静态字段或返回它们的方法。

用 JUnit 编写测试

在 JUnit 中,测试方法带有@Test注解。 为了运行该方法,JUnit 首先构造一个新的类实例,然后调用带注解的方法。 测试抛出的任何异常将由 JUnit 报告为失败。 如果未引发任何异常,则假定测试成功。

import java.util.ArrayList;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class Example {
	@BeforeClass
	public static void setup() {
	}

	@Before
	public void setupThis() {
	}

	@Test
	public void method() {
		org.junit.Assert.assertTrue(new ArrayList().isEmpty());
	}

	@After
	public void tearThis() {
	}

	@AfterClass
	public static void tear() {
	}
}

测试套件

使用 JUnit 测试套件,您可以运行分散到多个测试类中的测试。 在 JUnit 中,@RunWith@Suite注解都用于运行套件测试。

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)

@Suite.SuiteClasses({
   TestJunit1.class,
   TestJunit2.class
})

public class JunitTestSuite {   
}  

断言

断言有助于通过测试用例的实际输出来验证期望的输出。 所有的断言都在org.junit.Assert类中。 所有的断言方法都是static,它使代码可读性更好。

import static org.junit.Assert.*;

@Test
public void method() {
	assertTrue(new ArrayList().isEmpty());
}

假设

假设表明测试有意义的条件。 失败的假设并不意味着代码已损坏,但是测试没有提供有用的信息。 假设基本上是指“如果这些条件不适用,请勿运行此测试”。 默认的 JUnit 运行器将跳过带有失败假设的测试。

import org.junit.Test;
import static org.junit.Assume.*;

public class Example {
	public class AppTest {
	    @Test
	    void testOnDev() 
	    {
	        System.setProperty("ENV", "DEV");
	        assumeTrue("DEV".equals(System.getProperty("ENV")));
	    }

	    @Test
	    void testOnProd() 
	    {
	        System.setProperty("ENV", "PROD");
	        assumeFalse("DEV".equals(System.getProperty("ENV")));  
	    }
	}
}

总结

毫无疑问,JUnit 是 Java 技术中使用最多且功能最强大的单元测试框架。 它具有易于学习的曲线,并且易于遵循编码实践。 大多数 IDE 都内置了对 IDE 自身内部 junit 测试执行的支持,这使其对开发人员更加友好。

参考:

JUnit 文档

JUnit 测试套件示例

原文: https://howtodoinjava.com/junit/how-to-execute-junit-testcases-in-test-suite/

JUnit 测试套件帮助对测试进行分组和批量执行。 在大多数情况下,不需要为所有测试类分别执行测试。 测试套件有助于实现此分组。

在 JUnit 中,可以使用这些注解创建并执行测试套件。

  1. @RunWith
  2. @SuiteClasses

阅读更多: JUnit5 测试套件

1. JUnit 测试套件示例

1.1 测试类

下面给出的是 JUnit 测试类。

package com.howtodoinjava.junit;

import junit.framework.Assert;

import org.junit.Test;

public class TestFeatureOne {
	@Test
	public void testFirstFeature()
	{
		Assert.assertTrue(true);
	}
}

package com.howtodoinjava.junit;

import junit.framework.Assert;

import org.junit.Test;

public class TestFeatureTwo {
	@Test
	public void testSecondFeature()
	{
		Assert.assertTrue(true);
	}
}

1.2 创建 junit 测试套件

要仅在功能之上运行,我们可以编写这样的套件。

package com.howtodoinjava.junit.suite;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

import com.howtodoinjava.junit.TestFeatureOne;
import com.howtodoinjava.junit.TestFeatureTwo;

@RunWith(Suite.class)
@SuiteClasses({ TestFeatureOne.class, TestFeatureTwo.class })
public class TestFeatureSuite {
	//
}

1.3 执行 junit 测试套件

您可以使用JUnitCore从应用代码运行测试套件。

Result result = JUnitCore.runClasses(testCase);

for (Failure failure : result.getFailures())
{
    System.out.println(failure.toString());
}

学习愉快!

Maven – 创建 Java 源文件夹

原文: https://howtodoinjava.com/maven/how-to-create-java-source-folders-in-maven-web-application/

这是一个非常奇怪的问题,但这是事实,当您使用“-DarchetypeArtifactId=maven-archetype-webapp”创建 Maven Web 应用时,没有创建 Java 源文件夹。 而是创建了一个资源文件夹。 当为您的 Web 应用创建 war 文件时,放置在resources文件夹中的文件将放置在您的类路径中。

要创建 Java 源文件夹,必须手动创建它们。 然后将这些文件夹添加到您的生成配置中。

1)创建 Maven Web 应用

要创建 maven Web 应用,请在您的工作目录中运行以下命令。

//No line breaks in below command

mvn archetype:generate -DgroupId=com.howtodoinjava.demo -DartifactId=springmvcexample -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false

2)上面的命令将为工作目录内的 Web 应用特定 Maven 文件夹结构。 现在使用以下命令将 Eclipse 支持(或对您最喜欢的 IDE 的支持)添加到此 Web 应用中。

//Change current working directory to application folder
cd springmvcexample

//add eclipse support
mvn eclipse:eclipse

3)将 Web 应用作为现有 Maven 项目导入到 Eclipse 中

import-existing-maven-project

import-existing-maven-project-step-2

default-maven-directory-structure

4)手动创建源文件夹(是的! 需要手动执行)

add-new-folders-in-maven-web-project

5)使用命令 Maven >“更新项目”更新项目构建配置

update-maven-project

updated-source-folders

就这样。 您的源文件夹已准备就绪。

祝您学习愉快!

JUnit JUnitCore示例

原文: https://howtodoinjava.com/junit/how-to-execute-junit-testcases-with-junitcore/

在任何以增量方式构建的应用中,通常希望只要引入新功能,我们就只能运行某些测试。 可以使用 JUnit 框架的JUnitCore类来实现。

JUnitCore是 JUnit 包中的内置类,它基于外观设计模式JUnitCore类仅用于运行指定的测试类。

阅读更多: JUnit5 测试套件

1. JUnitCore示例

假设在应用发行版中有两个新功能。 这些功能通过两个接口公开。 假设接口名称为FeatureOneFeatureTwo

1.1 要测试的功能

这两个功能的 JUnit 测试可以如下:

package com.howtodoinjava.junit;

import junit.framework.Assert;

import org.junit.Test;

public class TestFeatureOne {
	@Test
	public void testFirstFeature()
	{
		Assert.assertTrue(true);
	}
}

package com.howtodoinjava.junit;

import junit.framework.Assert;

import org.junit.Test;

public class TestFeatureTwo {
	@Test
	public void testSecondFeature()
	{
		Assert.assertTrue(true);
	}
}

1.2 使用JUnitCore运行测试

要仅针对上述功能运行测试,我们可以编写这样的套件。

package com.howtodoinjava.junit.suite;

import java.util.ArrayList;
import java.util.List;

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

import com.howtodoinjava.junit.TestFeatureOne;
import com.howtodoinjava.junit.TestFeatureTwo;

@SuppressWarnings("rawtypes")
public class WithJUnitCore
{
	public static void main(String[] args)
	{
		List testCases = new ArrayList();

		//Add test cases
		testCases.add(TestFeatureOne.class);
		testCases.add(TestFeatureTwo.class);

		for (Class testCase : testCases)
        {
            runTestCase(testCase);
        }
	}

    private static void runTestCase(Class testCase)
    {
        Result result = JUnitCore.runClasses(testCase);

        for (Failure failure : result.getFailures())
        {
            System.out.println(failure.toString());
        }
    }
}

2. JUnitCore在命令提示符下运行测试

要从命令提示符手动运行测试类,我们可以从控制台运行以下命令。 给出所有测试类的名称,以空格分隔。

$ java org.junit.runner.JUnitCore TestFeatureOne TestFeatureTwo 

3. JUnitCore运行所有测试

强烈建议创建 JUnit 套件,并在所有测试用例中执行应用。 这将需要一些工作,但是仍然是在 JUnit 中执行所有测试的最佳方法。

@RunWith(Suite.class)
@SuiteClasses({ TestFeatureOne.class, TestFeatureTwo.class })
public class TestFeatureSuite {
	//
}

学习愉快!

参考:

JUnitCore Java 文档

使用 Maven 执行 JUnit 测试用例

原文: https://howtodoinjava.com/junit/executing-junit-testcases-with-maven/

Maven 是用于项目依赖和构建管理的好工具。 它可以用于运行项目的 Junit 测试用例。 在本文中,我将展示一些简单但有用的命令示例,以各种方式运行测试用例。

为了演示,我使用以下命令创建了一个 maven java 项目

mvn archetype:generate -DgroupId=com.howtodoinjava.junit -DartifactId=mavenJunitDemo
-DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

然后,我在测试文件夹中创建了一个测试类,如下所示。

package com.howtodoinjava.junit;

import org.junit.Test;

public class TestSurefire {

	@Test
	public void testcaseFirst()
	{
		System.out.println("First testcase executed");
	}

	@Test
	public void testcaseSecond()
	{
		System.out.println("Second testcase executed");
	}

	@Test
	public void testcaseThird()
	{
		System.out.println("Third testcase executed");
	}

	@Test
	public void otherTestcase()
	{
		System.out.println("Another testcase executed");
	}
}

让我们检查一下 maven 测试命令并查看其输出:

1)使用命令“mvn test”运行所有测试用例:该命令将运行测试文件夹中存在的所有测试用例,而不考虑其他任何条件。

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.howtodoinjava.junit.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.023 sec
Running com.howtodoinjava.junit.TestSurefire
Another testcase executed
First testcase executed
Third testcase executed
Second testcase executed
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.02 sec

Results :

Tests run: 5, Failures: 0, Errors: 0, Skipped: 0

2)仅使用“-Dtest=TestSurefire test”执行特定的测试类:这将执行测试类TestSurefire中的所有测试用例。

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.howtodoinjava.junit.TestSurefire
Another testcase executed
First testcase executed
Third testcase executed
Second testcase executed
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.033 sec

Results :

Tests run: 4, Failures: 0, Errors: 0, Skipped: 0

您可以在命令中使用多个测试类,甚至可以在测试类名称中使用通配符来匹配一组测试类。 例如mvn -Dtest=TestSurefire,TestOth*Class test

3)仅使用“mvn -Dtest=TestSurefire#testcaseFirst test”在测试类中测试某个测试用例:此命令将仅执行单个测试用例方法,即testcaseFirst()

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.howtodoinjava.junit.TestSurefire
First testcase executed
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.034 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

4)使用通配符映射测试多个测试用例,例如 “mvn -Dtest=TestSurefire#testcase* test”:这将有助于在一个简单的短命令中运行多个具有相似名称的测试用例。

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.howtodoinjava.junit.TestSurefire
First testcase executed
Second testcase executed
Third testcase executed
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.034 sec

Results :

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0

给我留言不清楚,或者我缺少的东西。

祝您学习愉快!

JUnit4 – 基于假设的测试用例

原文: https://howtodoinjava.com/junit/assumption-based-testcases-in-junit-4/

Junit 是 Java 编程语言的单元测试框架。 如果您想阅读有关最佳实践的信息,以进行 junit 测试,那么这里是一份出色的指南供您参考。

在本文中,我正在写有关编写 junit 测试用例的信息,这些用例仅在运行时满足特定条件时才能运行。 例如,我只想在其他网络服务启动时才运行测试用例。 如果服务中断,我不想通过测试。

在 junit 中,可以使用“org.junit.Assume”进行上述操作。 以下是示例用法:

@Test
public void testIfVersioonGreaterThan4()
{
	String versionNumber = "7"; //Get it from configuration on runtime
	Assume.assumeTrue(Integer.valueOf(versionNumber) == 7);
	System.out.println("Test executed");
}

仅当应用版本大于 7 时,以上测试用例才会执行。它实际上是一个很棒的功能,使我们能够编写功能特定的测试用例而不必担心。

当执行上述测试用例时,如果应用版本大于 7 或小于 7,则仅使用@Ignore注解而忽略测试用例。 某些 IDE 可能显示出它们执行了测试用例,但实际上并没有执行而只是被忽略了。 您可以通过查看日志进行验证。

如果要忽略单个 java 类中的所有测试用例,则可以在带注解的@Before方法中使用它。 所有测试用例都将以这种方式被忽略。

package com.howtodoinjava.test.junit;

import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;

public class JunitAssumptionTest {

	@Before
	public void setUp() {
		String versionNumber = "7"; //Get it from configuration on runtime
		Assume.assumeTrue(Integer.valueOf(versionNumber) == 7);
	}

	@Test
	public void testIfVersioonGreaterThan4()
	{
		System.out.println("Test executed");
	}

}

基于假设的测试用例在以下情况下很有用:

  • 针对特定应用版本运行测试用例
  • 仅在某些网络资源(或任何外部服务)可用时运行测试用例
  • 仅在特定语言环境中运行测试用例
  • 仅在特定执行环境下运行测试用例

根据需要,可能还有其他情况。 让我知道你有什么问题吗?

祝您学习愉快!

Junit 预期异常测试用例示例

原文: https://howtodoinjava.com/junit/junit-testcases-which-expects-exception-on-runtime/

Junit 是 Java 编程语言的单元测试框架。 如果您想阅读有关最佳实践的信息,以进行 junit 测试,那么这里是一份出色的指南供您参考。

在本文中,我正在编写一个示例测试用例,期望在运行时引发异常。 如果它获得预期的异常,则测试通过。 如果未检测到预期的异常,则测试用例失败。

如果您希望应用因非常荒谬的输入而失败,这些类型的测试用例将非常有用。

package com.howtodoinjava.test.junit;

import org.junit.Test;

public class ExpectedExceptionTest
{
	//This test case fails because it was expecting ArithmeticException
	@Test(expected = ArithmeticException.class)
	public void expectArithmeticException()
	{
		System.out.println("Everything was fine here !!");
	}

	//This test case fails because it was expecting ArithmeticException
	@Test(expected = ArithmeticException.class)
	public void expectArithmeticException2()
	{
		throw new NullPointerException();
	}

	//This test case passes because it was expecting NullPointerException
	@Test(expected = NullPointerException.class)
	public void expectNullPointerException()
	{
		//some code which throw NullPointerException in run time
		throw new NullPointerException();
	}
}

在上述 3 个测试用例中,前两个失败是因为他们期望ArithmeticException,而在执行测试用例时并没有得到。

第三个测试用例获得通过,因为它期望NullPointerException并被测试用例抛出。

这样,您可以编写依赖于某些异常的测试用例,以测试失败时应用的行为。

祝您学习愉快!

JUnit 测试监听器– JUnit RunListener示例

原文: https://howtodoinjava.com/junit/how-to-add-listner-in-junit-testcases/

监听器通常可以帮助监听我们感兴趣的事件。 这可能有几个原因。 例如,我们添加了监听器以添加特定的日志,在 Java GUI 编程中处理 UI 事件等。

JUnit 还支持通过RunListener类执行测试时添加监听器。 该监听器可用于从改进日志记录到测试特定逻辑的各种目的。

1. JUnit RunListener示例

1.1 JUnit 测试类

我们仅在下面举例说明两个测试类。 我们将监视为这些类编写的测试而打印的日志。

package com.howtodoinjava.junit;

import junit.framework.Assert;

import org.junit.Test;

public class TestFeatureOne {
	@Test
	public void testFirstFeature()
	{
		Assert.assertTrue(true);
	}
}

package com.howtodoinjava.junit;

import junit.framework.Assert;

import org.junit.Ignore;
import org.junit.Test;

public class TestFeatureTwo {
	@Test
	public void testSecondFeature()
	{
		Assert.assertTrue(true);
	}

	@Test
	@Ignore
	public void testSecondFeatureIngored()
	{
		Assert.assertTrue(true);
	}
}

1.2 JUnit 测试监听器

让我们编写运行监听器。 该监听器将扩展 JUnit 提供的RunListener类。

我们可以完全不包含任何方法来覆盖任何数量的方法RunListener类。

package com.howtodoinjava.junit.suite;

import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;

public class ExecutionListener extends RunListener
{
	/**
	 * Called before any tests have been run.
	 * */
	public void testRunStarted(Description description)	throws java.lang.Exception
	{
		System.out.println("Number of tests to execute : " + description.testCount());
	}

	/**
	 *  Called when all tests have finished
	 * */
	public void testRunFinished(Result result) throws java.lang.Exception
	{
		System.out.println("Number of tests executed : " + result.getRunCount());
	}

	/**
	 *  Called when an atomic test is about to be started.
	 * */
	public void testStarted(Description description) throws java.lang.Exception
	{
		System.out.println("Starting execution of test case : "+ description.getMethodName());
	}

	/**
	 *  Called when an atomic test has finished, whether the test succeeds or fails.
	 * */
	public void testFinished(Description description) throws java.lang.Exception
	{
		System.out.println("Finished execution of test case : "+ description.getMethodName());
	}

	/**
	 *  Called when an atomic test fails.
	 * */
	public void testFailure(Failure failure) throws java.lang.Exception
	{
		System.out.println("Execution of test case failed : "+ failure.getMessage());
	}

	/**
	 *  Called when a test will not be run, generally because a test method is annotated with Ignore.
	 * */
	public void testIgnored(Description description) throws java.lang.Exception
	{
		System.out.println("Execution of test case ignored : "+ description.getMethodName());
	}
}

2. JUnit 监听器执行

现在,让我们运行测试并观察监听器的输出。

package com.howtodoinjava.junit.suite;

import org.junit.runner.JUnitCore;

import com.howtodoinjava.junit.TestFeatureOne;
import com.howtodoinjava.junit.TestFeatureTwo;

public class ExecuteWithRunListener
{
	public static void main(String[] args)
	{
		JUnitCore runner = new JUnitCore();
		<strong>//Adding listener here</strong>
		runner.addListener(new ExecutionListener());
		runner.run(TestFeatureOne.class, TestFeatureTwo.class);
	}
}

程序输出。

Number of tests to execute : 3

Starting execution of test case : testFirstFeature
Finished execution of test case : testFirstFeature

Starting execution of test case : testSecondFeature
Finished execution of test case : testSecondFeature

Execution of test case ignored : testSecondFeatureIngored

Number of tests executed : 2

显然,添加监听器可通过改进的日志记录支持对测试执行提供额外的控制。

学习愉快!

JUnit 测试超时 – JUnit5 超时示例

原文: https://howtodoinjava.com/junit/how-to-force-timeout-in-jnuit-testcase-execution/

学习编写具有超时行为的 JUnit 测试。 如果任何测试未在给定的时限内完成执行,则 JUnit 将停止执行。 还学习使用断言使用 JUnit5 测试超时

1. 为什么要编写 JUnit 超时测试?

有时,我们必须编写 JUnit 测试,以访问网络上的某些外部系统。 绝对不能 100% 地确定在执行测试用例时这些外部系统将可用。 这就是为什么在编写具有外部依赖项的测试用例时,建议使用 JUnit 框架的timeout属性。

这也被认为是 JUnit 最佳实践

每个 JUnit 测试都在一个新线程中运行。 如果在测试完成之前经过了指定的超时,则将通过Thread.interrupt()中断其执行。

2. JUnit 测试超时示例 – timeout属性

为了指定某个测试用例的超时周期,在注解@Test上提到“timeout”属性。 例如,@Test(timeout = 1000)

超时时间以毫秒指定。

@Test(timeout = 500)
public void testInfiniteTametakingLoop() throws InterruptedException 
{
	while (true)
	{
		Thread.currentThread().sleep(1000);
	}
}

在上述测试中,执行将在 5 秒后超时,并显示以下消息。

java.lang.Exception: test timed out after 5000 milliseconds

2. JUnit 全局超时示例 – 超时规则

可以为类中的所有测试定义 JUnit 规则,而不是为所有测试分别指定超时属性。

@Rule
public Timeout globalTimeout = Timeout.seconds(2);

@Test 	//Pass
public void testInfiniteTametakingLoop1() throws InterruptedException 
{
	while (true)
	{
		Thread.currentThread().sleep(1000);
	}
}

@Test 	//Fail
public void testInfiniteTametakingLoop2() throws InterruptedException 
{
	while (true)
	{
		Thread.currentThread().sleep(5000);
	}
}

在上面的示例中,第一个测试将通过,而第二个测试将失败。

请注意,以上超时时间@Rule同样适用于@Before@After方法。 因此,请小心使用。

4. 使用断言的 JUnit5 超时

JUnit5 中,我们可以使用断言强制测试超时。

import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;
import static org.junit.jupiter.api.Assertions.assertTimeout;

@Test
void timeoutNotExceeded() 
{
    //The following assertion succeeds.
    assertTimeout(ofMinutes(2), () -> {
        // Perform task that takes less than 2 minutes.
    });
}

@Test
void timeoutExceeded() 
{
    // The following assertion fails with an error message similar to:
    // execution exceeded timeout of 10 ms by 91 ms
    assertTimeout(ofMillis(10), () -> {
        // Simulate task that takes more than 10 ms.
        Thread.sleep(100);
    });
}

学习愉快!

JUnit 有序测试执行示例

原文: https://howtodoinjava.com/junit/ordered-testcases-execution-in-junit-4/

编写 JUnit 有序测试案例被认为是不良做法。 但是,如果仍然遇到测试用例排序是唯一出路的情况,则可以使用MethodSorters类。

1. JUnit 方法排序器

从 JUnit4.11 版本开始引入MethodSorters。 此类声明了三种执行顺序,可以在测试用例中使用它们。

  1. MethodSorters.DEFAULT – 以确定但不可预测的顺序对测试方法进行排序。
  2. MethodSorters.JVM - 按照 JVM 返回的顺序保留测试方法。
  3. MethodSorters.NAME_ASCENDING按方法名称依字典顺序对测试方法进行排序,并使用Method.toString()作为平局。

2. JUnit 有序测试示例 – NAME_ASCENDING

让我们看看如何在 JUnit 中编写和执行有序的测试。

import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

//Running test cases in order of method names in ascending order
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class OrderedTestCasesExecution 
{
	@Test
	public void secondTest() {
		System.out.println("Executing second test");
	}

	@Test
	public void firstTest() {
		System.out.println("Executing first test");
	}

	@Test
	public void thirdTest() {
		System.out.println("Executing third test");
	}
}

程序输出。

Executing first test
Executing second test
Executing third test

2. JUnit 有序测试示例 – JVM

现在使用JVM选项执行相同的测试。

package corejava.test.junit;

import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

//Running test cases in order of method names in ascending order
@FixMethodOrder(MethodSorters.JVM)
public class OrderedTestCasesExecution {
	@Test
	public void secondTest() {
		System.out.println("Executing second test");
	}

	@Test
	public void firstTest() {
		System.out.println("Executing first test");
	}

	@Test
	public void thirdTest() {
		System.out.println("Executing third test");
	}
}

程序输出:

Executing third test
Executing first test
Executing second test

显然,只有NAME_ASCENDING顺序可让您控制真正的顺序,而其他两个选项在测试执行顺序序列中对于开发人员而言却无法提供足够的可预测性。

在此 JUnit 教程中,我们学习了编写 JUnit 顺序测试的过程。 让我知道你的想法。

学习愉快!

参考:

MethodSorters

JUnit 参数化测试示例

原文: https://howtodoinjava.com/junit/how-to-write-parameterized-testcases-with-junit-4/

在本 JUnit 教程中,学习创建和执行 junit 参数化测试。 参数化测试是正常测试,它使用不同的测试参数反复执行。 它可以帮助开发人员在使用不同输入类型执行相同测试以测试函数健壮性,以及可能的函数边界上面节省时间。

1. JUnit Maven 依赖项

下面是 maven 依赖项,我们应该在测试示例代码之前添加到您的 maven 项目中。

<!-- Junit -->
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.11</version>
	<scope>test</scope>
</dependency>

<dependency>
	<groupId>junit</groupId>
	<artifactId>junit-dep</artifactId>
	<version>4.11</version>
	<scope>test</scope>
</dependency>

2. 具有构造器参数的 JUnit 参数化测试

参数化测试使用@RunWith注解以及@Parameters注解来馈送输入。

2.1 要测试的类

下面是测试类,我们将为其编写测试用例。

package corejava.test.junit;

public final class MathUtils 
{
	//Return square of a function
	public static int square(final int number) {
		return number * number;
	}
}

2.2 参数化测试

让我们为上述数学工具类编写参数化测试。


package corejava.test.junit;

import java.util.Arrays;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class JunitTestsWithParameters {

	// @Parameters annotation marks this method as parameters provider
	@Parameters(name = "Run #Square of : {0}^2={1}")
	public static Iterable<Object []> data() 
	{
		return Arrays.asList(new Object[][] { { 1, 1 }, 
											{ 2, 4 }, 
											{ 3, 19 }, 
											{ 4, 16 }, 
											{ 5, 25 } });
	}

	// Our two parameters
	private final int input;
	private final int resultExpected;

	// Constructor is initialized with one set of parameters every time
	public JunitTestsWithParameters(final int input, final int result) 
	{
		this.input = input;
		this.resultExpected = result;
	}

	@Test
	public void testUserMapping() {
		// You can use here assert also
		Assert.assertEquals(resultExpected, MathUtils.square(input));
	}
}

请注意:

  1. 我们必须按照给定的方式声明参数。
  2. 参数传递给类的构造器以设置变量,因此可以在测试用例中使用。
  3. 参数类的返回类型为“List[]”,要使用的数据类型已限于字符串或原始值

现在检查程序输出。

JUnit4 test execution

测试执行结果

3. 带有字段注入的 JUnit 参数化测试

为了传递参数进行测试,我们可以通过字段注入传递参数,而不是通过构造器传递参数。 在这种方法中,我们声明确切的字段数作为输入参数。 每个字段一个参数。

让我们通过场注入重新测试我们的MathUtils类。 请注意,我们如何用@Parameter注解的字段替换构造器。

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class JunitTestsWithFieldInjection {

    @Parameters(name = "Run #Square of : {0}^2={1}")
    public static Iterable<Object[]> data() {
        return Arrays.asList(new Object[][] { { 1, 1 }, { 2, 4 }, { 3, 9 }, { 4, 16 }, { 5, 25 } });
    }

    @Parameter(value = 0)
    public int input;

    @Parameter(value = 1)
    public int resultExpected;

    @Test
    public void testSquare() 
    {
        Assert.assertEquals(resultExpected, MathUtils.square(input));
    }
}

3.1 单个字段注入

如果仅要注入一个字段,则无需在@Parameter注解中放置value属性。 默认值始终为“value = 0”。

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class JunitTestsWithParameters {

    @Parameters(name = "Argument number {0} is positive")
    public static Iterable<Object[]> data() {
        return Arrays.asList(new Object[][] { { 0 }, { 1 }, { 2 }, { 3 }, { 4 } });
    }

    @Parameter
    public int input;

    @Test
    public void testPositiveNumber() 
    {
        Assert.assertEquals(true, input >= 0);
    }
}

在本文中,我们学习了如何创建参数化测试,并使用不同的参数集运行测试的多次迭代。 它有助于测试带有参数的方法。

学习愉快!

Junit 参数化测试 – @Theory@DataPoints

原文: https://howtodoinjava.com/junit/junit-parameterized-testcases-with-theory-and-datapoints/

在我之前关于该主题的文章中,介绍了如何编写带有@Parameters注解的参数化测试用例。 如果我选择了正确的单词,那么这种方法会很混乱并且不太可读。 不必要地需要大量关注。 嗯,还有另一种方法,您可以借助@Theory@DataPoints等注解在 Junit 中编写参数化测试用例。

我将以以前的帖子为例,并将其转换为新方法。 这是有道理的,因为在此之后我们将能够比较哪些变化以及与以前的方法有何不同。

1)使用@DataPoints馈送输入数据

在此,仅注解已从@Parameters更改为@DataPoints。 其余的概念是相同的。

以前,提供输入的方法是:

@Parameters(name = "Run #Square of : {0}^2={1}")
public static Iterable<Object []> data() {
	return Arrays.asList(new Object[][] { { 1, 1 }, { 2, 4 }, { 3, 19 },
			{ 4, 16 }, { 5, 25 } });
}

现在它是:

@DataPoints
public static int[][] integers()
{
  return new int[][]{{1, 1}, {2, 4}, {3, 9}, {4, 16}, {5, 25}, {}};
}

请注意,您可以使用@DataPoint注解分别编写输入。

@DataPoint
public static int[] input6 = new int[]{6, 36};

@DataPoint
public static int[] input7 = new int[]{7, 49};

我将返回类型从“Iterable<object[]>”更改为“int[][]”,因为这些输入馈送到测试用例的方式略有不同。 您将在下一部分中看到不同之处。

2)用@Theory编写测试用例

从结构上讲,基于理论的类比参数化测试类简单。 类声明应使用@RunWith(Theories.class)进行注解,并且必须提供两个实体:

  1. 生成并返回测试数据的数据方法
  2. 一个理论

数据方法必须使用@DataPoints进行注解,每个理论都必须使用@Theory进行注解。 与普通的单元测试一样,每个理论都应至少包含一个断言。

在以前的方法中,我们编写了如下的测试用例:

@Test
public void testUserMapping() {
	// You can use here assert also
	Assert.assertEquals(resultExpected, MathUtils.square(input));
}

其中inputresultExpected被声明为类成员,并使用参数化构造器进行填充。 如您所见,上面的testUserMapping()方法没有任何参数。

在新方法中,测试使用@Theory注解进行注解。 例如:

@Theory
public void testSquares(final int[] inputs)
{
  Assume.assumeTrue(inputs[0] > 0 && inputs[1] > 0);
  Assert.assertEquals(inputs[1], MathUtils.square(inputs[0]));
}

您会看到参数现在已成为测试用例的一部分,这是概念的最佳组成部分。 假定True()确保参数为正数,并且assertEquals()检查我们需要测试的函数逻辑。

要调整上述测试用例,请按以下方式用@RunWith注解类。

@RunWith(Theories.class)
public class JunitTestsWithParameters
{
	//Testcases
}

如果您认为某些测试用例在执行操作时可能会引发异常,请使用@Rule注解和ExpectedException类对其进行处理。 下面给出一个更完整的工作示例:

package test.junit.theory;

import org.junit.Assert;
import org.junit.Assume;
import org.junit.Rule;
import org.junit.experimental.theories.DataPoint;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

@RunWith(Theories.class)
public class JunitTestsWithParameters
{
   @Rule
   public ExpectedException expectedException = ExpectedException.none();

   @DataPoints
   public static int[][] integers()
   {
      return new int[][]{{1, 1}, {2, 4}, {3, 9}, {4, 16}, {5, 25}, {}};
   }

   @DataPoint
   public static int[] input6 = new int[]{6, 36};
   @DataPoint
   public static int[] input7 = new int[]{7, 49};

   @Theory
   public void testSquares(final int[] inputs)
   {
      Assume.assumeTrue(inputs.length == 2);
      Assume.assumeTrue(inputs[0] > 0 && inputs[1] > 0);
      Assert.assertEquals(inputs[1], MathUtils.square(inputs[0]));
   }

   @Theory
   public void testBlankArrays(final int[] inputs)
   {
      Assume.assumeTrue(inputs.length == 0);
      expectedException.expect(ArrayIndexOutOfBoundsException.class);
      Assert.assertEquals(inputs[1], MathUtils.square(inputs[0]));
   }
}

运行上述测试用例,结果将如下所示:

Junit Theory Example Output

Junit 理论示例输出

请注意,将测试数据从测试/理论实现中分离出来,除了简洁以外,还可以带来另一个积极影响:您可能会开始考虑独立于要测试的实际内容的测试数据。

但是同时,您应该已经注意到,没有办法将特定结果与特定数据点配对。 当您可以以断言的形式表达数据点与预期结果之间的一般关系时,以及当该关系对于所有数据都成立时,应该使用理论。

因此,应在适当考虑的情况下在理论和参数化测试用例之间谨慎选择。 它们不是参数化测试用例的精确替代,而是它们的补充。

祝您学习愉快!

JUnit – 使用TemporaryFolder@Rule创建临时文件/文件夹

原文: https://howtodoinjava.com/junit/junit-creating-temporary-filefolder-using-temporaryfolder-rule/

很多时候,我们需要创建 junit 单元测试,我们需要创建临时文件夹或临时文件来执行测试用例。 很多时候,我们依赖于在特定位置有一个临时文件夹并在那里生成所有临时文件。 嗯,它有它自己的缺点。 主要缺点是您需要手动清理这些文件。

Junit 带有TemporaryFolder类,可用于生成临时文件夹。

TemporaryFolder规则允许创建测试方法完成(无论通过还是失败)时应删除的文件和文件夹。 此规则不检查删除是否成功。 万一删除失败,将不会引发任何异常。

TemporaryFolder规则的示例用法为:

public static class HasTempFolder {
        @Rule
        public TemporaryFolder folder= new TemporaryFolder();

        @Test
        public void testUsingTempFolder() throws IOException {
                File createdFile= folder.newFile("myfile.txt");
                File createdFolder= folder.newFolder("subfolder");
                // ...
        }
 }

让我们做一个快速的测试用例,看看它是如何工作的。

import java.io.File;
import java.io.IOException;

import junit.framework.Assert;

import org.apache.commons.io.FileUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class TemporaryFolderTest {

   @Rule
   public TemporaryFolder tempFolder = new TemporaryFolder();

   @Test
   public void testWrite() throws IOException {
     // Create a temporary file.
     final File tempFile = tempFolder.newFile("tempFile.txt");

     // Write something to it.
     FileUtils.writeStringToFile(tempFile, "hello world");

     // Read it from temp file
     final String s = FileUtils.readFileToString(tempFile);

     // Verify the content
     Assert.assertEquals("hello world", s);

     //Note: File is guaranteed to be deleted after the test finishes.
   }
 }

这确实是 Junit 的简单实用功能。 下次使用它,您会发现它有很大的帮助。

祝您学习愉快!

Maven BOM – 物料清单依赖项

原文: https://howtodoinjava.com/maven/maven-bom-bill-of-materials-dependency/

如果您在项目中为依赖项管理使用过 maven,那么您必须至少遇到一个问题,或者可能更多。 问题是版本不匹配。 通常,当您获得一些依赖项并将其相关的依赖项与特定版本协作时,就会发生这种情况。 并且,如果您已经包含了具有不同版本号的那些依赖项,则它们可能在编译时以及运行时都面临不良结果。

理想情况下,为避免上述问题,您需要明确排除相关的依赖项,但您很有可能会忘记这样做。

要解决版本不匹配的问题,您可以使用“物料清单”(BOM)依赖项的概念。 BOM 依赖项跟踪版本号,并确保所有依赖项(直接和传递)都处于同一版本。

1. 如何添加 Maven BOM 依赖

Maven 为此提供了一个标签dependencyManagement。 您需要按如下所示在此标记中添加 maven bom 信息。 我以 Spring Bom 文件为例。

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-framework-bom</artifactId>
            <version>4.0.1.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

使用 BOM 的另一个好处是,根据 Spring 框架工件,您不再需要指定version属性。 因此它将完全正常。

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
    </dependency>
<dependencies>

每个项目都有自己的 Maven Bom 文件

请注意,没有通用或通用的 Bom 文件。 每个项目(如果支持此功能)都提供自己的 Bom 文件并管理其相关依赖项的版本。

以下是各种 Bom 文件的几个示例:

1)RESTEasy Maven BOM 依赖项

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-bom</artifactId>
			<version>3.0.6.Final</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

2. JBOSS Maven BOM 依赖项

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.jboss.bom</groupId>
			<artifactId>jboss-javaee-6.0-with-tools</artifactId>
			<version>${some.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement> 

3)Spring Maven BOM 依赖项

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-framework-bom</artifactId>
            <version>4.0.1.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

4)Jersey Maven BOM 依赖项

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey</groupId>
            <artifactId>jersey-bom</artifactId>
            <version>${jersey.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

这些只是几个例子。 因此,下次您在 Maven 中工作时,请尝试探索此功能。

3. Maven BOM 与 POM

首先,BOM 是普通的pom.xml文件 - 它们不包含源代码,它们的唯一目的是声明其捆绑的模块。 它定义了将在库中创建的所有工件的版本。 其他希望使用该库的项目应将此 pom 导入到其 pom 的dependencyManagement部分。

POM 文件不仅仅是依赖项。 例如组织和许可证,项目所在的 URL,项目的依赖项,插件,配置文件等等。 它还控制项目的完整构建过程。

学习愉快!

参考:

POM – Maven 文档

Maven 依赖机制

TestNG 教程

TestNG 教程

原文: https://howtodoinjava.com/java-testng-tutorials/

TestNG 是一个受 JUnit 和 NUnit 启发的测试框架,但引入了一些新功能,使其更强大,更易于使用。 TestNG 旨在涵盖所有类别的测试:单元测试,功能测试,端到端测试,集成测试等等。

此页面列出了此博客中有关 TestNG 的所有可用教程。 在 TestNG 上发布新教程后,将立即更新此页面。

安装与配置

将 TestNG 安装到 Eclipse 上并执行第一个测试

本教程详细讨论以下主题:

  • TestNG 的介绍
  • TestNG 的优点
  • 将 TestNG 安装到 Eclipse
  • 创建具有 TestNG 依赖项的 Java 项目
  • 创建您的第一个 TestNG 类
  • 运行 TestNG 测试

启用 TestNG

注解教程

TestNG 使用注解来帮助开发人员编写测试。 了解 TestNG 提供的所有注解及其简要说明。

前后注解

前后注解主要用于在执行测试方法之前和之后执行特定的代码集。 这些用于基本上在测试执行开始之前设置一些变量或配置,然后在测试执行结束之后清除所有这些内容。

预期的异常和预期的消息

在编写单元测试时,在某些情况下,我们需要验证在执行过程中程序是否引发了异常。 TestNG 提供了一种功能,可以通过允许用户指定在执行过程中预期由测试方法引发的异常类型来测试这种情况。 它支持为验证提供多个值。 如果测试引发的异常不属于用户输入列表,则测试方法将标记为失败。

如何禁用/忽略测试方法

在执行 TestNG 测试时,在某些情况下,您可能不得不禁用特定的测试或一组测试才能执行。 例如,考虑由于某些测试属于某些无法执行的场景而导致功能中存在严重错误的场景。 由于问题已经被发现,我们可能需要禁用上述测试方案。

超时测试

在运行测试时,某些情况下可能会卡住某些测试,或者可能花费比预期更长的时间。 在这种情况下,您可能需要将所述测试用例标记为失败,然后继续。 在本教程中,我们将学习将 TestNG 测试配置为在某些配置的持续时间后超时。

通过testng.xml@Parameters注解传递参数

TestNG 的重要功能之一是参数化。 此功能允许用户将参数值作为参数传递给测试方法。 通过使用@Parameters注解支持此功能。

高级概念

通过@DataProvider传递参数

TestNG 提供的一个重要功能是DataProvider功能。 它可以帮助您编写数据驱动的测试,这实际上意味着同一测试方法可以针对不同的数据集多次运行。 它有助于为测试方法提供复杂的参数,因为不可能从 XML 做到这一点。

测试组,元组,默认组示例

分组测试方法是 TestNG 最重要的功能之一。 在 TestNG 中,用户可以将多种测试方法分组为一个命名组。 您还可以执行属于一个或多个组的一组特定的测试方法。 此功能允许将测试方法分为不同的部分或模块。

了解本教程中的以下主题:

  • 分组测试示例
  • 通过 Eclipse 运行 TestNG 组
  • 通过testng.xml运行 TestNG 组
  • 编写属于多个组的测试
  • 包括和排除组
  • 使用正则表达式
  • 默认组
  • 组群

依赖测试示例

依赖项是 TestNG 中的一项功能,它允许测试方法依赖于单个或一组测试方法。 这将有助于执行要在测试方法之前执行的一组测试。 仅当“依赖方法”是同一类或任何继承的基类的一部分(即在扩展类时)时,方法依赖才有效。

@Factory注解

了解 TestNG 提供的@Factory注解。 @Factory允许根据某些数据集或条件在运行时创建测试。

@Factory@DataProvider之间的区别

在学习 TestNG 时,许多人在阅读@DataProvider@Factory注解时会感到困惑 - 什么时候使用什么? 还有什么更好的呢? 让我们看看它们的两个功能。

执行并行测试

在软件方面,并行性或多线程定义为软件,操作系统或程序同时执行另一个程序的多个部分或子组件的能力。 TestNG 允许测试以并行或多线程模式运行。 这意味着基于测试套件的配置,不同的线程将同时启动,并在其中执行测试方法。 与正常执行相比,这给用户带来了很多优势,主要是减少了执行时间和验证多线程代码的能力。

了解以下主题:

  • 并行测试执行的优势
  • 并行运行测试方法
  • 并行运行测试类
  • 在套件中并行运行测试
  • 配置测试方法以在多个线程中运行

使用 Maven 构建执行 TestNG 测试

了解如何使用 Maven 构建文件(即pom.xml文件)执行 TestNG 测试。 如果您打算使测试自动化并使它们成为项目构建过程本身的一部分,则此知识很重要。

祝您学习愉快!

TestNG 教程(使用 Eclipse)

原文: https://howtodoinjava.com/testng/testng-tutorial-with-eclipse/

您可能知道测试的过程是根据预期的工作方式来验证和验证某个软件或硬件是否在工作。 测试是软件开发生命周期(SDLC)的重要组成部分,因为它有助于提高开发产品的质量。 测试有多种类型和级别,例如白盒,黑盒,单元,集成,系统,验收,性能,安全性,功能,非功能性等等。 这些类型的测试中的每一种都是使用自动化工具手动完成或通过自动化完成的。

顾名思义,测试自动化是指自动化测试过程。 测试自动化具有以多种方式运行测试的优势,例如以固定的间隔或作为应用构建的一部分。 这有助于在开发本身的初始阶段识别错误,从而缩短了产品时间线并提高了产品质量。 它还有助于减少重复的手动测试工作,并使手动测试团队能够专注于测试新功能和复杂场景。

Table of Contents

Introduction of TestNG
Advantages of TestNG
Installing TestNG onto Eclipse
Creating Java Project with TestNG Dependencies
Creating your first TestNG class
Running TestNG test

TestNG 的介绍

TestNG,其中 NG 代表“下一代”,是受 JUnit (Java)和 NUnit(C#)启发的测试自动化框架。 它可以用于单元,功能,集成和端到端测试。 TestNG 在短时间内获得了很大的普及,并且是 Java 开发人员中使用最广泛的测试框架之一。 它主要使用 Java 注解来配置和编写测试方法。

TestNG 在 JUnit4 上具有的一些功能是:

  • 额外的之前和之后注解,例如“之前/之后”套件和“之前/之后”组
  • 依赖项测试
  • 测试方法分组
  • 多线程执行
  • 内置报告框架

它是用 Java 编写的,可以与 Java 以及与 Java 相关的语言(例如 Groovy)一起使用。 在 TestNG 中,套件和测试主要通过 XML 文件进行配置或描述。 默认情况下,文件的名称是testng.xml,但是如果需要,我们可以给它提供任何其他名称。 TestNG 允许用户通过 XML 文件进行测试配置,并允许他们在测试套件中包括(或排除)各自的程序包,类和方法。 它还允许用户将测试方法分组为特定的命名组,并将其包括或排除为测试执行的一部分。

TestNG 的优点

现在,让我们发现 TestNG 提供的更多功能/优势。

  1. 多个前后注解选项
  2. 基于 XML 的测试配置和测试套件定义
  3. 相关方法
  4. 小组/小组
  5. 依赖群体
  6. 测试方法的参数化
  7. 数据驱动的测试
  8. 多线程执行
  9. 更好的报告

我们将在接下来的教程中更详细地讨论这些功能。

将 TestNG 安装到 Eclipse

在我们可以下载并开始使用 TestNG 之前,请确保您的系统上已安装 Java JDK5 或更高版本。 还要确保在系统路径中设置了 JDK。

如果您只想下载 TestNG JAR,可以从以下 URL 获取:

TestNG Jar – http://testng.org/testng-6.8.zip

现在,让我们开始将 TestNG 安装到 Eclipse 上。 我将尝试捕获该过程中的所有步骤。

1)打开您的 Eclipse 应用。

2)转到“帮助 | 安装新软件”。

Eclipse - Go to Help - Install New Software

3)单击“使用”文本框旁边的“添加…”按钮

Click on the Add

4)在名称框中输入 TestNG 站点,在位置中输入 URL http://beust.com/eclipse。 完成后,单击“确定”按钮。

Enter TestNG site into the Name box

5)单击确定,将 TestNG 更新站点添加到 Eclipse。 可用的软件窗口将显示可在 TestNG 站点下下载的工具。

Select TestNG and click on Next

6)选择 TestNG,然后单击“Next”

7)Eclipse 将计算下载所选 TestNG 插件的软件要求,并显示“安装详细信息”屏幕。 在详细信息屏幕上单击“下一步”。

8)接受许可信息,然后单击“完成”。 这将开始将 TestNG 插件下载并安装到 Eclipse。

download and installation of the TestNG

9)如果出现以下警告窗口,请单击“确定”按钮。

TestNG warning window

10)安装完成后,Eclipse 将提示您重新启动它。 在窗口提示上单击“是”。

11)重新启动 Eclipse 后,通过转至“窗口 | 偏好”来验证 TestNG 插件的安装。 您将在首选项窗口下看到一个 TestNG 部分。

Verify the TestNG plugin installation

我们已成功将 TestNG 插件安装到 Eclipse 安装中。 这将帮助我们使用 Eclipse 执行 TestNG 测试或套件。

创建具有 TestNG 依赖项的 Java 项目

在编写第一个 TestNG 测试之前,我们必须在 Eclipse 中创建一个 Java 项目并添加我们的 TestNG 测试依赖项。

1)转到“文件 | 新增 | 其他”。 将显示一个带有多个选项的窗口。

2)选择 Java 项目,如以下屏幕截图所示,然后单击“下一步”。

Select Java Project

3)在下一个屏幕上,输入 Java 项目的项目名称,例如TestNGExamples,如以下屏幕截图所示,然后单击“完成”:

Enter a Project name for a Java project

这将在 Eclipse 中创建一个新的 Java 项目。

4)现在转到“项目 | 属性”在“属性”窗口左侧选择“Java 构建路径”,如以下屏幕截图所示。 这将显示新创建的项目的构建路径。

Select Java Build Path

5)单击库选项卡,然后单击“添加库…”选项

6)在“添加库”窗口中选择 TestNG,如以下屏幕截图所示,然后单击“下一步”:

Add Library

7)在下一个窗口中单击“完成”。 这会将 TestNG 库添加到您的 Eclipse 项目中。

Click on Finish Adding TestNG

太好了,我们已经在 Eclipse 中成功创建了一个新的 Java 项目,并将 TestNG 库添加到了项目的构建路径。

创建您的第一个 TestNG 类

执行以下步骤来创建您的第一个 TestNG 类:

1)转到“文件 | 新增 | 其他”。 这将在 Eclipse 中打开一个新的“添加”向导窗口。

Add wizard window

2)从“添加”向导窗口中选择 TestNG 类,然后单击“下一步”。

Add wizard window-2

3)在下一个窗口上,单击“浏览”按钮,然后选择需要在其中添加类的 Java 项目。

select the Java project

4)输入包名称和测试类名称,然后单击“完成”。

Enter the package name

5)此窗口还为您提供了一个选项,可以在创建新的 TestNG 类时选择不同的注解。 如果选择,则插件将在生成类时为这些注解生成伪方法。 这将为您的项目添加一个新的 TestNG 类。

package com.howtodoinjava.test;

import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

public class MyFirstTest {
	@Test
	public void f() {
	}

	@BeforeTest
	public void beforeTest() {
	}

	@AfterTest
	public void afterTest() {
	}
}

我们已成功将新的 TestNG 测试类添加到 Eclipse 中新创建的 Java 项目中。 随时根据需要修改代码。 现在,让我们通过 Eclipse 运行新创建的测试类。

运行 TestNG 测试

执行以下步骤以通过 Eclipse 运行测试:

1)在 Eclipse 中选择 Java 项目,然后转到“运行 | 运行配置”

Run Configuration for TestNG

2)在给定的选项中选择 TestNG,然后单击“新建”按钮以创建新配置。

Create new TestNG configuration

3)请注意,TestNG 插件提供了多个用于运行测试用例的选项,如下所示:

  • :使用此选项,您可以提供类名称和程序包,以仅运行所述特定测试类。
  • 方法:使用此方法,您只能在测试类中运行特定的方法。
  • :如果您想运行属于特定 TestNG 组的特定测试方法,则可以在此处输入用于执行它们的方法。
  • :如果要在包中执行所有测试,则可以在此框中指定这些测试。
  • 套件:如果您具有testing.xml文件形式的套件文件,则可以在此处选择要执行的文件。

让我们输入配置名称TestNGRunConfig,然后在“类”部分下选择新创建的类,然后单击“应用”。

4)现在,如果您想运行新创建的配置,只需在单击“应用”之后单击“运行”。 这将编译并运行我们编写的 TestNG 测试类。 测试执行的结果显示在 Eclipse 的“控制台”和“结果”窗口中,如以下屏幕快照所示。

[TestNG] Running:
  C:\Users\somelocalpath\testng-customsuite.xml

PASSED: f
===============================================
    Default test
    Tests run: 1, Failures: 0, Skips: 0
===============================================

===============================================
Default suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

[TestNG] Time taken by org.testng.reporters.XMLReporter@177b3cd: 23 ms
[TestNG] Time taken by [FailedReporter passed=0 failed=0 skipped=0]: 0 ms
[TestNG] Time taken by org.testng.reporters.jq.Main@b8deef: 46 ms
[TestNG] Time taken by org.testng.reporters.JUnitReportReporter@10ab323: 12 ms
[TestNG] Time taken by org.testng.reporters.EmailableReporter2@5e176f: 13 ms
[TestNG] Time taken by org.testng.reporters.SuiteHTMLReporter@d1e89e: 142 ms

您还可以通过选择测试类,然后右键单击它,从菜单中选择“运行方式”,然后选择“TestNG 测试”,来运行测试类。

Run TestNG test class

TestNG 教程中,我们了解了 TestNG,TestNG 提供的功能,将 TestNG 插件安装到 Eclipse 中以及通过 Eclipse 编写和执行 TestNG 测试类。

在接下来的教程中,我们将学习 TestNG 的更多高级功能。

祝您学习愉快!

参考http://testng.org/

如何从 Maven 运行testng.xml

原文: https://howtodoinjava.com/testng/how-to-execute-testng-tests-with-maven-build/

在此 testng maven 示例中,我们将学习如何使用 maven 构建文件pom.xml执行 testng 测试。 如果您打算自动执行测试测试,并使它们成为项目构建过程本身的一部分,则此知识很重要。

1. 编写 TestNG 测试

以下是我们已经在两个先前教程中编写的两个测试。

public class RegularExpressionGroupTest
{
	@Test(groups = { "include-test-one" })
	public void testMethodOne() {
		System.out.println("Test method one");
	}

	@Test(groups = { "include-test-two" })
	public void testMethodTwo() {
		System.out.println("Test method two");
	}

	@Test(groups = { "test-one-exclude" })
	public void testMethodThree() {
		System.out.println("Test method three");
	}

	@Test(groups = { "test-two-exclude" })
	public void testMethodFour() {
		System.out.println("Test method Four");
	}
}

public class ParallelSuiteTest 
{
	String testName = "";

	@BeforeTest
	@Parameters({ "test-name" })
	public void beforeTest(String testName) {
		this.testName = testName;
		long id = Thread.currentThread().getId();
		System.out.println("Before test " + testName + ". Thread id is: " + id);
	}

	@BeforeClass
	public void beforeClass() {
		long id = Thread.currentThread().getId();
		System.out.println("Before test-class " + testName + ". Thread id is: "
				+ id);
	}

	@Test
	public void testMethodOne() {
		long id = Thread.currentThread().getId();
		System.out.println("Sample test-method " + testName
				+ ". Thread id is: " + id);
	}

	@AfterClass
	public void afterClass() {
		long id = Thread.currentThread().getId();
		System.out.println("After test-method  " + testName
				+ ". Thread id is: " + id);
	}

	@AfterTest
	public void afterTest() {
		long id = Thread.currentThread().getId();
		System.out.println("After test  " + testName + ". Thread id is: " + id);
	}
}

2. 编写 TestNG 套件

让我们看看两个测试的 testng 套件文件testng.xmlsuites-test-testng.xml

<suite name="Group of group Suite" verbose="1">
  <test name="Group of group Test">
    <groups>
      <define name="include-group">
        <include name="include-test-one" />
        <include name="include-test-two" />
      </define>
      <define name="exclude-group">
        <include name="test-one-exclude" />
        <include name="test-two-exclude" />
      </define>
      <run>
        <include name="include-group" />
        <exclude name="exclude-group" />
      </run>
    </groups>
    <classes>
      <class name="com.howtodoinjava.groupExamples.RegularExpressionGroupTest" />
    </classes>
  </test>
</suite>

<suite name="Parallel tests" parallel="tests" thread-count="2" >
  <test name="Test One">
    <parameter name="test-name" value="Test One"/>
    <classes>
      <class name="com.howtodoinjava.parallelism.ParallelSuiteTest" />
    </classes>
  </test>
  <test name="Test Two">
    <parameter name="test-name" value="Test Two"/>
    <classes>
      <class name="com.howtodoinjava.parallelism.ParallelSuiteTest" />
    </classes>
  </test>
</suite>

3. 在 Maven 中添加 testng 套件

在项目根目录下的pom.xml下添加。 下面给出的文件为 maven 构建项目定义了不同的配置。 每个部分的功能已在代码中作为内联注解提及。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.test.maven</groupId>
	<artifactId>sample-maven-build</artifactId>
	<version>1</version>
	<name>sample-maven-build</name>
	<build>
		<!-- Source directory configuration -->
		<sourceDirectory>src</sourceDirectory>
		<plugins>
			<!-- Following plugin executes the testng tests -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>2.14.1</version>
				<configuration>
					<!-- Suite testng xml file to consider for test execution -->
					<suiteXmlFiles>
						<suiteXmlFile>testng.xml</suiteXmlFile>
						<suiteXmlFile>suites-test-testng.xml</suiteXmlFile>
					</suiteXmlFiles>
				</configuration>
			</plugin>
			<!-- Compiler plugin configures the java version to be usedfor compiling 
				the code -->
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<!-- Dependency libraries to include for compilation -->
		<dependency>
			<groupId>org.testng</groupId>
			<artifactId>testng</artifactId>
			<version>6.3.1</version>
		</dependency>
	</dependencies>
</project>

Maven 可以与任何连续集成系统一起使用。 插件maven-surefire-plugin用于配置和执行测试。 这里,所述插件用于为 TestNG 测试配置testng.xmlsuites-test-testng.xml并生成测试报告。

插件maven-compiler-plugin用于帮助编译代码并使用特定的 JDK 版本进行编译。

4. TestNG Maven 示例

现在在命令提示符下使用命令“mvn test”在pom.xml文件上方执行。 您将获得以下结果。

C:\somepath\TestNGExamples>mvn test
[INFO] Scanning for projects...
[WARNING]
[WARNING] Some problems were encountered while building the effective model forcom.test.maven:sample-maven-build:jar:1
[WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-compiler-plugin is missing. @ line 27, column 12
[WARNING]
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING]
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING]
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building sample-maven-build 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ sample-maven-build ---
[debug] execute contextualize
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources,i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory C:\BAML\DFCCUI\workspace_personal\TestNGExamples\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ sample-maven-build ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.5:testResources (default-testResources) @ sample-maven-build ---
[debug] execute contextualize
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources,i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory C:\BAML\DFCCUI\workspace_personal\TestNGExamples\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ sample-maven-build ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.14.1:test (default-test) @ sample-maven-build ---
[INFO] No tests to run.
[INFO] Surefire report directory: C:\BAML\DFCCUI\workspace_personal\TestNGExamples\target\surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running TestSuite
Test method one
Test method two
Before test Test One. Thread id is: 10
Before test Test Two. Thread id is: 11
Before test-class Test One. Thread id is: 10
Before test-class Test Two. Thread id is: 11
Sample test-method Test One. Thread id is: 10
Sample test-method Test Two. Thread id is: 11
After test-method  Test One. Thread id is: 10
After test-method  Test Two. Thread id is: 11
After test  Test One. Thread id is: 10
After test  Test Two. Thread id is: 11
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.86 sec

Results :

Tests run: 4, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.346s
[INFO] Finished at: Wed Dec 03 12:02:26 IST 2014
[INFO] Final Memory: 4M/15M
[INFO] ------------------------------------------------------------------------

随时在pom.xml文件上方添加/更新,以满足您的需求。

学习愉快!

TestNG 注解教程

原文: https://howtodoinjava.com/testng/testng-annotations-tutorial/

TestNG 使用注解来帮助开发人员编写测试。 下表是包含有关 TestNG 提供的所有注解的信息及其简要说明的表。 我将在针对其用法的单独教程中详细介绍它们。

TestNG 测试配置注解

注解 描述
@BeforeSuite 带注解的方法将在 TestNG 套件内声明的任何测试之前执行。
@AfterSuite 带注解的方法将在 TestNG 套件内声明的任何测试之后执行。
@BeforeTest 带注解的方法将在 TestNG 套件中声明的每个测试部分之前执行。
@AfterTest 带注解的方法将在 TestNG 套件中声明的每个测试部分之后执行。
@BeforeGroups 带注解的方法将在执行指定组的任何测试方法之前运行。
@AfterGroups 带注解的方法将在指定组的任何测试方法执行后运行。
@BeforeClass 带注解的方法在测试类的任何测试方法之前执行。
@AfterClass 在执行测试类的每个测试方法之后,将执行带注解的方法。
@BeforeMethod 这些带注解的方法在每个测试方法执行之前执行。
@AfterMethod 这些带注解的方法在每个测试方法执行后执行。
@DataProvider 将方法标记为测试方法的数据提供方法。 所述方法必须返回对象双数组(Object[][])作为数据。
@Factory 将带注解的方法标记为返回类对象数组(Object[])的工厂。 然后,这些类对象将被 TestNG 用作测试类。 这用于运行一组具有不同值的测试用例。
@Listeners 应用于测试类。 定义一组扩展org.testng.ITestNGListener的测试监听器类。 帮助跟踪执行状态和记录目的。
Parameters 此注解用于将参数传递给测试方法。 这些参数值在运行时使用testng.xml配置文件提供。
Test 将类或方法标记为测试方法。 如果在类级别使用,则类的所有公共方法都将被视为测试方法。

当您感到困惑时,请参考 TestNG 注解的表格。

祝您学习愉快!

TestNG – 预期异常和预期消息教程

原文: https://howtodoinjava.com/testng/testng-expected-exception-and-expected-message-tutorial/

在编写单元测试时,在某些情况下,我们需要验证在执行过程中程序是否引发了异常。 通过允许用户指定在执行过程中测试方法引发的异常类型,TestNG 提供了一种测试此类情况的功能。 它支持为验证提供多个值。 如果测试引发的异常不属于用户输入列表,则测试方法将标记为失败。

让我们创建一个示例测试,并了解异常测试如何在 TestNG 中工作。

@Test ( expectedExceptions = { IOException.class, NullPointerException.class } )

让我们看一个例子,以更好地理解它。

预期异常测试示例

在下面的测试中,我们有两种测试方法,即exceptionTestOne()exceptionTestTwo()。 这里exceptionTestOne()抛出IOException,而exceptionTestTwo()抛出Exception。 在使用Test注解时,使用ExpectedExceptions属性值提到了在运行这些测试时验证的预期异常。

public class ExceptionTestDemo 
{
	@Test(expectedExceptions = { IOException.class })
	public void exceptionTestOne() throws Exception {
		throw new IOException();
	}

	@Test(expectedExceptions = { IOException.class, NullPointerException.class })
	public void exceptionTestTwo() throws Exception {
		throw new Exception();
	}
}

以上测试运行的输出如下:

[TestNG] Running: C:\Users\somepath\testng-customsuite.xml

PASSED: exceptionTestOne
FAILED: exceptionTestTwo

org.testng.TestException: 
Expected exception java.io.IOException but got org.testng.TestException: 
Expected exception java.io.IOException but got java.lang.Exception
	at org.testng.internal.Invoker.handleInvocationResults(Invoker.java:1497)
	at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1245)
	at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
	at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
	at org.testng.TestRunner.privateRun(TestRunner.java:767)
	at org.testng.TestRunner.run(TestRunner.java:617)
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:334)
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329)
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
	at org.testng.SuiteRunner.run(SuiteRunner.java:240)
	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
	at org.testng.TestNG.run(TestNG.java:1057)
	at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
	at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
	at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)
Caused by: org.testng.TestException: 
Expected exception java.io.IOException but got java.lang.Exception
	at org.testng.internal.Invoker.handleInvocationResults(Invoker.java:1497)
	at org.testng.internal.Invoker.invokeMethod(Invoker.java:754)
	at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
	at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
	... 16 more
Caused by: java.lang.Exception
	at com.howtodoinjava.test.ExceptionTestDemo.exceptionTestTwo(ExceptionTestDemo.java:16)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
	at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
	... 18 more

===============================================
    Default test
    Tests run: 2, Failures: 1, Skips: 0
===============================================

从测试结果中可以看出,exceptionTestTwo()在执行期间被 TestNG 标记为失败。 测试失败,因为所述方法引发的异常与ExpectedExceptions列表中提供的异常列表不匹配。

带有验证消息的预期异常测试的示例

您还可以根据测试引发的异常消息来验证测试。 正则表达式也可以用于验证错误消息,可以使用.*.完成此操作,具体取决于正则表达式的位置,我们可以在验证异常消息时使用它进行模式匹配,例如开始,包含,结束于。

让我们学习如何根据抛出的异常消息编写异常测试。

public class ExceptionTestDemo 
{
	@Test(expectedExceptions = { IOException.class }, expectedExceptionsMessageRegExp = "Pass Message test")
	public void exceptionTestOne() throws Exception {
		throw new IOException("Pass Message test");
	}

	@Test(expectedExceptions = { IOException.class }, expectedExceptionsMessageRegExp = ".* Message .*")
	public void exceptionTestTwo() throws Exception {
		throw new IOException("Pass Message test");
	}

	@Test(expectedExceptions = { IOException.class }, expectedExceptionsMessageRegExp = "Pass Message test")
	public void exceptionTestThree() throws Exception {
		throw new IOException("Fail Message test");
	}
}

以上测试运行的输出如下:

[TestNG] Running: C:\Users\somepath\testng-customsuite.xml

PASSED: exceptionTestOne
PASSED: exceptionTestTwo
FAILED: exceptionTestThree

org.testng.TestException: 
Expected exception java.io.IOException but got org.testng.TestException: 
The exception was thrown with the wrong message: expected "Pass Message test" but got "Fail Message test"
	at org.testng.internal.Invoker.handleInvocationResults(Invoker.java:1497)
	at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1245)
	at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
	at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
	at org.testng.TestRunner.privateRun(TestRunner.java:767)
	at org.testng.TestRunner.run(TestRunner.java:617)
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:334)
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329)
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
	at org.testng.SuiteRunner.run(SuiteRunner.java:240)
	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
	at org.testng.TestNG.run(TestNG.java:1057)
	at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
	at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
	at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)
Caused by: org.testng.TestException: 
The exception was thrown with the wrong message: expected "Pass Message test" but got "Fail Message test"
	at org.testng.internal.Invoker.handleInvocationResults(Invoker.java:1481)
	at org.testng.internal.Invoker.invokeMethod(Invoker.java:754)
	at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
	at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
	... 16 more
Caused by: java.io.IOException: Fail Message test
	at com.howtodoinjava.test.ExceptionTestDemo.exceptionTestThree(ExceptionTestDemo.java:21)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
	at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
	... 18 more

===============================================
    Default test
    Tests run: 3, Failures: 1, Skips: 0
===============================================

在上述测试方法中,exceptionTestThree()失败,因为预期的消息不匹配。

祝您学习愉快!

TestNG – 如何禁用/忽略测试方法

原文: https://howtodoinjava.com/testng/testng-how-to-disableignore-test-method/

在执行 TestNG 测试时,可能在某些情况下可能需要禁用特定测试或一组测试才能执行。 例如,考虑由于某些测试属于某些无法执行的场景而导致功能中存在严重错误的场景。 由于问题已经被发现,我们可能需要禁止执行上述测试方案

禁用 TestNG 中的测试可以通过将@Test注解的enable属性设置为false来实现。 这将使所述测试方法无法作为测试套件的一部分执行。 如果在类级别为Test注解设置了此属性,则将禁用该类内的所有公共方法。

@Test( enabled=false )

让我们看一个例子,以更好地理解它。

禁用测试方法的示例

在下面的测试中,我们有三种测试方法,即testMethodOne()testMethodTwo()testMethodThree()。 其中testMethodTwo()需要禁用。

public class DisableTestDemo 
{
	@Test(enabled = true)
	public void testMethodOne() {
		System.out.println("Test method one.");
	}

	@Test(enabled = false)
	public void testMethodTwo() {
		System.out.println("Test method two.");
	}

	@Test
	public void testMethodThree() {
		System.out.println("Test method three.");
	}
}

以上测试运行的输出如下:

[TestNG] Running:  C:\Users\somepath\testng-customsuite.xml

Test method one.
Test method three.

PASSED: testMethodOne
PASSED: testMethodThree

===============================================
    Default test
    Tests run: 2, Failures: 0, Skips: 0
===============================================

如您在前面的结果中所看到的,TestNG 仅执行了两种方法。 测试执行将忽略属性启用值为false的方法。 默认情况下,属性启用值为true,因此即使未指定属性值,您也可以看到名称为testMethodThree()的测试方法已由 TestNG 执行。

学习愉快!

TestNG 并行执行测试,类和套件

原文: https://howtodoinjava.com/testng/testng-executing-parallel-tests/

TestNG 并行执行测试,类和套件以及示例。 了解如何在多个线程中并行或单个运行 testng 测试和套件。

在软件方面,并行性或多线程定义为软件,操作系统或程序同时执行另一个程序的多个部分或子组件的能力。 TestNG 允许测试以并行或多线程模式运行。 这意味着基于测试套件的配置,不同的线程将同时启动,并在其中执行测试方法。 与正常执行相比,这给用户带来了很多优势,主要是减少了执行时间和验证多线程代码的能力。

Table Of Contents

1\. Advantages of parallel rests execution
2\. Run parallel testcases
3\. Run test classes in parallel
4\. Run tests suite in parallel
5\. Configure a testcase to run in multiple threads

1. 并行执行测试的优点

并行或多线程执行可以为用户提供很多优势。 以下是两个:

  1. 减少了执行时间 – 并行执行测试时,会同时执行多个测试,因此减少了执行测试所需的总时间。
  2. 允许多线程测试 – 使用此功能,我们可以编写测试来验证应用中的某些多线程代码。

并行测试执行被 QA 行业广泛用于功能自动化测试。 此功能可帮助质量检查人员配置其测试,以使其易于同时在多个浏览器或操作系统中执行。

在 TestNG 中可以使用多种不同的方式配置并行功能。

2. 并行运行 testng 测试用例

TestNG 提供了多种方法在多线程条件中执行测试,其中一种方法是在单个线程中执行每种测试方法。 此模式显着减少了执行时间,因为可以并行执行更多测试,从而减少了总执行时间。

testng 中用于并行测试执行的 Java 程序。 这是一个如何使用单个类文件中的testng xml并行运行方法的示例。

package com.howtodoinjava.parallelism;

import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class ParallelMethodTest 
{
	@BeforeMethod
	public void beforeMethod() {
		long id = Thread.currentThread().getId();
		System.out.println("Before test-method. Thread id is: " + id);
	}

	@Test
	public void testMethodsOne() {
		long id = Thread.currentThread().getId();
		System.out.println("Simple test-method One. Thread id is: " + id);
	}

	@Test
	public void testMethodsTwo() {
		long id = Thread.currentThread().getId();
		System.out.println("Simple test-method Two. Thread id is: " + id);
	}

	@AfterMethod
	public void afterMethod() {
		long id = Thread.currentThread().getId();
		System.out.println("After test-method. Thread id is: " + id);
	}
}

前面的测试类包含两个测试方法,它们在执行时将消息打印到控制台上。 使用Thread.currentThread.getId()代码求值正在执行当前方法的线程的 ID。

它还包含beforeafter方法,它们在执行时还将当前线程的线程 ID 打印到控制台上。

在项目下创建一个名为methods-test-testng.xml的新文件,并编写以下代码。

<suite name="Test-method Suite" parallel="methods" thread-count="2" >
  <test name="Test-method test" group-by-instances="true">
    <classes>
      <class name="com.howtodoinjava.parallelism.ParallelMethodTest" />
    </classes>
  </test>
</suite>

在 Eclipse 中选择此文件并将其作为 TestNG 套件运行。 您将在“控制台”窗口中看到以下测试结果:

Before test-method. Thread id is: 10
Before test-method. Thread id is: 9
Simple test-method Two. Thread id is: 10
Simple test-method One. Thread id is: 9
After test-method. Thread id is: 10
After test-method. Thread id is: 9

注意,上一个屏幕截图中显示的Id值在控制台输出中可能会不同。 Id值是在执行期间由 Java 虚拟机(JVM)在运行时分配的。

先前的测试结果清楚地表明,每种测试方法及其各自的beforeafter方法都是在不同的线程中执行的。 这由控制台上打印的线程的 ID 标识。

2. 并行运行 testng 测试类

在此示例中,我们将了解并行执行 testng 测试类; 属于测试执行的每个测试类都将在其自己的线程中执行。

public class ParallelClassesTestOne 
{
	@BeforeClass
	public void beforeClass() {
		long id = Thread.currentThread().getId();
		System.out.println("Before test-class. Thread id is: " + id);
	}

	@Test
	public void testMethodOne() {
		long id = Thread.currentThread().getId();
		System.out.println("Sample test-method One. Thread id is: " + id);
	}

	@Test
	public void testMethodTwo() {
		long id = Thread.currentThread().getId();
		System.out.println("Sample test-method Two. Thread id is: " + id);
	}

	@AfterClass
	public void afterClass() {
		long id = Thread.currentThread().getId();
		System.out.println("After test-class. Thread id is: " + id);
	}
}

public class ParallelClassesTestTwo 
{
	@BeforeClass
	public void beforeClass() {
		long id = Thread.currentThread().getId();
		System.out.println("Before test-class. Thread id is: " + id);
	}

	@Test
	public void testMethodOne() {
		long id = Thread.currentThread().getId();
		System.out.println("Sample test-method One. Thread id is: " + id);
	}

	@Test
	public void testMethodTwo() {
		long id = Thread.currentThread().getId();
		System.out.println("Sample test-method Two. Thread id is: " + id);
	}

	@AfterClass
	public void afterClass() {
		long id = Thread.currentThread().getId();
		System.out.println("After test-class. Thread id is: " + id);
	}
}

在项目下创建一个名为classes-test-testng.xml的新文件,并编写以下代码。

<suite name="Test-class Suite" parallel="classes" thread-count="2" >
  <test name="Test-class test" >
    <classes>
      <class name="com.howtodoinjava.parallelism.ParallelClassesTestOne" />
      <class name="com.howtodoinjava.parallelism.ParallelClassesTestTwo" />
    </classes>
  </test>
</suite>

在 Eclipse 中选择此文件并将其作为 TestNG 套件运行。 您将在“控制台”窗口中看到以下测试结果:

Before test-class. Thread id is: 10
Before test-class. Thread id is: 9
Sample test-method One. Thread id is: 9
Sample test-method One. Thread id is: 10
Sample test-method Two. Thread id is: 10
After test-class. Thread id is: 10
Sample test-method Two. Thread id is: 9
After test-class. Thread id is: 9

先前的测试结果清楚地表明,每个测试类及其各自的beforeClassafterClass方法都是在不同的线程中执行的。 这由控制台上打印的线程的 ID 标识。

4. 并行运行 TestNG 套件

让我们学习一下在套件中并行执行每个测试,也就是说,作为测试套件执行一部分的每个测试都将在各自独立的线程中执行。

package com.howtodoinjava.parallelism;

import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class ParallelSuiteTest 
{
	String testName = "";

	@BeforeTest
	@Parameters({ "test-name" })
	public void beforeTest(String testName) {
		this.testName = testName;
		long id = Thread.currentThread().getId();
		System.out.println("Before test " + testName + ". Thread id is: " + id);
	}

	@BeforeClass
	public void beforeClass() {
		long id = Thread.currentThread().getId();
		System.out.println("Before test-class " + testName + ". Thread id is: "
				+ id);
	}

	@Test
	public void testMethodOne() {
		long id = Thread.currentThread().getId();
		System.out.println("Sample test-method " + testName
				+ ". Thread id is: " + id);
	}

	@AfterClass
	public void afterClass() {
		long id = Thread.currentThread().getId();
		System.out.println("After test-method  " + testName
				+ ". Thread id is: " + id);
	}

	@AfterTest
	public void afterTest() {
		long id = Thread.currentThread().getId();
		System.out.println("After test  " + testName + ". Thread id is: " + id);
	}
}

在项目下创建一个名为suite-test-testng.xml的新文件,并编写以下代码。

<suite name="Test-class Suite" parallel="tests" thread-count="2">
    <test name="Test-class test 1">
        <parameter name="test-name" value="test-method One" />
        <classes>
            <class name="com.howtodoinjava.parallelism.ParallelSuiteTest" />
        </classes>
    </test>
    <test name="Test-class test 2">
        <parameter name="test-name" value="test-method One" />
        <classes>
            <class name="com.howtodoinjava.parallelism.ParallelSuiteTest" />
        </classes>
    </test>
</suite>

在 Eclipse 中选择此文件并将其作为 TestNG 套件运行。 您将在“控制台”窗口中看到以下测试结果:

Before test Test One. Thread id is: 9
Before test Test Two. Thread id is: 10
Before test-class Test One. Thread id is: 9
Before test-class Test Two. Thread id is: 10
Sample test-method Test One. Thread id is: 9
Sample test-method Test Two. Thread id is: 10
After test-method  Test Two. Thread id is: 10
After test-method  Test One. Thread id is: 9
After test  Test One. Thread id is: 9
After test  Test Two. Thread id is: 10

先前的测试结果清楚地表明,套件中的每个测试都在其各自的线程中执行。 这由控制台上打印的线程的ID标识。

5. 配置 testng 测试以在多个线程中运行

前面我们讨论了如何以并行或多线程模式运行类,方法和测试。 TestNG 还提供了灵活性,可以配置要在多线程环境中运行的测试方法。 这是通过在方法上使用@Test注解时对其进行配置来实现的。

public class IndependentTest 
{
	@Test(threadPoolSize = 3, invocationCount = 6, timeOut = 1000)
	public void testMethod() 
	{
		Long id = Thread.currentThread().getId();
		System.out.println("Test method executing on thread with id: " + id);
	}
}

通过使用threadPoolSize属性以及“测试”注解,该方法被配置为在多线程模式下运行。 threadPoolSize的值设置为 3; 这将测试方法配置为在三个不同的线程中运行。

其他两个属性invocationCounttimeOut,将测试配置为多次调用,并且如果执行花费很多,则失败 更多时间。

在项目下创建一个名为independent-test-testng.xml的新文件,并编写以下代码。

<suite name="Independent test Suite" >
  <test name="Independent test">
    <classes>
     <class name="com.howtodoinjava.parallelism.IndependentTest" />
    </classes>
  </test>
</suite>

在 Eclipse 中选择此文件并将其作为 TestNG 套件运行。 您将在“控制台”窗口中看到以下测试结果:

Test method executing on thread with id: 11
Test method executing on thread with id: 10
Test method executing on thread with id: 9
Test method executing on thread with id: 11
Test method executing on thread with id: 11
Test method executing on thread with id: 10

在此,根据invocationCount属性值多次执行测试方法。 每次执行均在单独的线程中完成,该线程从测试报告输出中清晰可见。 当您只想在多线程模式下而不是整个测试套件中运行固定数量的测试方法时,此功能很有用。

学习愉快!

TestNG – 依赖测试示例

原文: https://howtodoinjava.com/testng/testng-dependent-tests-examples/

依赖项是 TestNG 中的一项功能,它允许测试方法依赖于单个或一组测试方法。 这将有助于执行一组要在测试方法之前执行的测试。 仅当“依赖方法”是同一类或任何继承的基类的一部分时(即,在扩展类时),方法依赖才有效。

在本教程中,我们将学习如何在 TestNG 中创建依赖测试。

Table of Contents

Test with single test method dependency
Test with multiple test methods dependencies
Inherited dependency test
Test that depends on a group

使用单一测试方法依赖项进行测试

TestNG 允许您创建一个示例测试方法,该方法依赖于同一类的另一个测试方法。

public class DependentTestExamples 
{
	@Test(dependsOnMethods = { "testTwo" })
	public void testOne() {
		System.out.println("Test method one");
	}

	@Test
	public void testTwo() {
		System.out.println("Test method two");
	}
}

前面的测试类包含两个测试方法,这些方法在执行时将消息名称打印到控制台上。 在此,测试方法testOne取决于测试方法testTwo。 在使用Test注解时,通过使用属性dependsOnMethods进行配置。

现在开始运行测试。

Test method two
Test method one
PASSED: testTwo
PASSED: testOne

在上面的测试结果中,您可以看到在测试方法一消息之前打印了测试方法二消息。 这表明testOne方法在testTwo之后执行,因为它取决于testTwo

使用多种测试方法依赖项进行测试

有时,可能需要一种测试方法依赖于其他多种方法。 作为依赖项支持的一部分,TestNG 很好地支持了此功能。

public class DependentTestExamples 
{
	@Test(dependsOnMethods = { "testTwo", "testThree" })
	public void testOne() {
		System.out.println("Test method one");
	}

	@Test
	public void testTwo() {
		System.out.println("Test method two");
	}

	@Test
	public void testThree() {
		System.out.println("Test method three");
	}
}

前面的测试类包含三种测试方法,这些方法在执行时会将消息名称打印到控制台上。 这里的测试方法testOne取决于测试方法testTwotestThree。 在使用Test注解时,通过使用属性dependsOnMethods进行配置。

现在开始运行测试。

Test method three
Test method two
Test method one
PASSED: testThree
PASSED: testTwo
PASSED: testOne

通过查看控制台消息,我们可以看到方法testTwotestThreetestOne之前执行。

继承的依赖测试

到目前为止,我们已经看到了相关测试方法属于同一类的示例。 只能针对属于同一类或任何继承的基类的测试方法提及对测试方法的依赖项。 让我们看看当依赖方法是继承的基类的一部分时,TestNG 如何执行测试方法。

public class ParentClassTest 
{
	@Test(dependsOnMethods = { "testTwo" })
	public void testOne() {
		System.out.println("Test method one");
	}

	@Test
	public void testTwo() {
		System.out.println("Test method two");
	}
}

public class DependentTestExamples extends ParentClassTest
{
	@Test(dependsOnMethods = { "testOne" })
	public void testThree() {
		System.out.println("Test three method in Inherited test");
	}

	@Test
	public void testFour() {
		System.out.println("Test four method in Inherited test");
	}
}

前面的测试类包含两个测试方法,这些方法在执行时将消息名称打印到控制台上。 这里的测试方法testThree取决于测试方法testOne。 在使用Test注解时,通过使用属性 dependsOnMethods进行配置。

现在开始运行测试。

Test four method in Inherited test
Test method two
Test method one
Test three method in Inherited test
PASSED: testFour
PASSED: testTwo
PASSED: testOne
PASSED: testThree

从测试结果中可以看出,执行的顺序为testFourtestTwotestOne,最后是testThree。 由于testThree依赖于testOnetestTwo,因此 TestNG 根据相关性执行所有测试方法,最后根据各自的测试方法执行所有测试方法。

依赖组的测试

与依赖方法类似,TestNG 还允许测试方法依赖组。 这样可以确保在依赖测试方法之前执行一组测试方法。

public class DependentTestExamples
{
	@Test(dependsOnGroups = { "test-group" })
	public void groupTestOne() {
		System.out.println("Group Test method one");
	}

	@Test(groups = { "test-group" })
	public void groupTestTwo() {
		System.out.println("Group test method two");
	}

	@Test(groups = { "test-group" })
	public void groupTestThree() {
		System.out.println("Group Test method three");
	}
}

前面的测试类包含两个测试方法,这些方法在执行时将消息名称打印到控制台上。 这里,测试方法testOne取决于测试方法testTwo。 这是通过在使用Test注解时使用属性dependsOnMethods来配置的。

现在开始运行测试。

Group Test method three
Group test method two
Group Test method one
PASSED: groupTestThree
PASSED: groupTestTwo
PASSED: groupTestOne

方法依赖项仅适用于属于同一类或一个继承的类中的其他方法,但不适用于不同类。 如果您需要一个存在于单独类中的测试方法,请执行以下操作: 您可以通过将所述测试方法分配给一个组并将依赖测试方法配置为依赖于该组来实现。

这些都与 TestNG 中的相关测试有关。 让我知道您是否有任何疑问。

学习愉快!

TestNG – 超时测试教程

原文: https://howtodoinjava.com/testng/testng-timeout-test-tutorial/

在运行测试时,某些情况下可能会卡住某些测试,或者可能花费比预期更长的时间。 在这种情况下,您可能需要将所述测试用例标记为失败,然后继续。 在本教程中,我们将学习将 TestNG 测试配置为在某些配置的持续时间后超时

TestNG 允许用户设置时间段,以等待测试完全执行。 超时可以通过两种方式配置:

  • 在套件级别:这将适用于上述 TestNG 测试套件中的所有测试
  • 在每个测试方法级别:这将适用于所述测试方法,并且如果在套件级别配置,则将覆盖时间段

要指定超时持续时间,请使用@Test注解的“timeOut”属性。

@Test ( timeOut = 500 )

让我们创建一个示例测试,并了解 TimeNG 在 TestNG 中的工作原理。

套件级别的超时测试示例

在下面的测试中,我们有两种测试方法,即timeTestOne()timeTestTwo()timeTestOne()将需要 1000 毫秒才能完全执行,而timeTestTwo()将需要 400 毫秒才能完全执行。 我们已经使用Thread.sleep()方法来强制执行时间。

public class TimeoutSuite 
{
	@Test
	public void timeTestOne() throws InterruptedException {
		Thread.sleep(1000);
		System.out.println("Time test method one");
	}

	@Test
	public void timeTestTwo() throws InterruptedException {
		Thread.sleep(400);
		System.out.println("Time test method two");
	}
}

现在将testng.xml文件添加到项目根目录,并将以下代码放入其中。 该代码将超时时间定义为 500ms。

<suite name="Time test Suite" time-out="500" verbose="1" >
  <test name="Timeout Test" >
    <classes>
      <class name="com.howtodoinjava.test.TimeoutSuite" />
    </classes>
  </test>
</suite>

现在使用testng.xml运行以上测试。 以上测试运行的输出如下:

[TestNG] Running: C:\somepath\TestNGExamples\testng.xml

Time test method two

===============================================
Time test Suite
Total tests run: 2, Failures: 1, Skips: 0
===============================================

从测试结果中可以看出,只有timeTestTwo()被执行,因为它的执行时间少于testng.xml文件中定义的超时时间。 timeTestOne()执行被取消,因为完成所需的时间超过配置的超时时间。

现在开始学习在测试方法级别设置超时的方法。

方法级别的超时测试示例

如前所述,您也可以在方法级别指定超时。 这将使您可以灵活地给每个单独的测试方法特定的运行时间。

public class TimeoutMethod 
{
	@Test(timeOut = 500)
	public void timeTestOne() throws InterruptedException {
		Thread.sleep(1000);
		System.out.println("Time test method one");
	}

	@Test(timeOut = 500)
	public void timeTestTwo() throws InterruptedException {
		Thread.sleep(400);
		System.out.println("Time test method two");
	}
}

以上测试运行的输出如下:

[[TestNG] Running: C:\Users\somepath\testng-customsuite.xml

Time test method two
PASSED: timeTestTwo
FAILED: timeTestOne

org.testng.internal.thread.ThreadTimeoutException: Method org.testng.internal.TestNGMethod.timeTestOne() didn't finish within the time-out 500

===============================================
    Default test
    Tests run: 2, Failures: 1, Skips: 0
===============================================

在上述测试方法中,timeTestOne()失败,因为它未在指定的超时时间内完全执行。

祝您学习愉快!

Maven 教程

在 Eclipse 中导入 Maven 远程原型目录

原文: https://howtodoinjava.com/eclipse/how-to-import-maven-remote-archetype-catalogs-in-eclipse/

本教程为在 Eclipse 中导入 Maven 远程原型目录提供了简单的说明,以使您在配置项目时获得领先。 使用原型自动导入所有基本的运行时依赖项,并使用最基本的配置来配置项目,您可以在数分钟内直接将其直接用于构建 HelloWorld 应用。

第 1 步:在 Eclipse 中打开 Maven 首选项

转到“Windows -> Preferences -> Maven -> Archetypes”。

Maven archetype option in eclipse

Eclipse 中的 Maven 原型选项

步骤 2:添加远程目录文件

单击添加远程目录按钮。 将目录文件填写为“http://repo1.maven.org/maven2/archetype-catalog.xml”和一些自定义描述。

Fill Archetype URL and name

填写原型网址和名称

单击确定按钮。 大功告成。

步骤 3:验证远程原型

为了验证您现在可以访问所有原型,请创建一个新的 maven 项目。

选择您创建的新的自定义目录。

Jersey remote archetypesJersey remote archetypes

Jersey 远程原型

第一次,将花费一些时间来收集所有原型。 您可以在 IDE 的右下角看到进度。 完成后,您应该可以看到原型。

填写项目的组 ID 和工件 ID。

Fill group id and artifact id

填写组 ID 和工件 ID

单击确定以创建项目。 您将在工作区上看到已完成所有依赖项和基本配置的新项目创建。

New maven project with auto configuration

具有自动配置的新 Maven 项目

如果您在任何步骤中遇到任何问题,请随时向我提问。

祝您学习愉快!

TestNG @Parameters – 测试参数示例

原文: https://howtodoinjava.com/testng/testng-parameters/

TestNG 的重要功能之一是参数化。 此功能允许用户将参数作为参数传递给测试。 通过使用 testng @Parameters注解支持此功能。

我们可以通过两种方式为 testng 测试提供参数值。

  1. 通过testng.xml XML 配置文件
  2. 通过DataProviders

@Parameters注解可用于任何@Before@After@Factory@Test注解方法。 它可以用于初始化变量并将其用于类,测试中,也可以用于整个测试执行。

1. TestNG @Parameters – 使用testng.xml测试参数

如果需要在运行时将一些简单的值(例如String类型)传递给测试方法,则可以使用这种通过 testng XML 配置文件发送参数值的方法。 您必须使用@Parameters注解将参数值传递给测试方法。

@Parameters({ "param-name" })

让我们编写一个简单的示例,通过 XML 配置文件将参数传递给测试方法。

1.1 测试

在下面的测试中,我们创建了一个包含多个方法的测试类,这些方法接受来自 testng 的参数。 在 testng XML 文件中的套件级别和测试级别都设置了参数值。

如果在套件级别定义,则在测试级别定义的任何参数值都将覆盖具有相同名称的参数值。 您可以在测试方法prameterTestThree()的测试三中看到这一点。

package com.howtodoinjava.test;

import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class ParameterTest {
	/**
	 * Following method takes one parameter as input. Value of the 
	 * said parameter is defined at suite level.
	 */
	@Parameters({ "suite-param" })
	@Test
	public void prameterTestOne(String param) {
		System.out.println("Test one suite param is: " + param);
	}

	/**
	 * Following method takes one parameter as input. Value of the
	 * said parameter is defined at test level.
	 */
	@Parameters({ "test-two-param" })
	@Test
	public void prameterTestTwo(String param) {
		System.out.println("Test two param is: " + param);
	}

	/**
	 * Following method takes two parameters as input. Value of the 
	 * test parameter is defined at test level. The suite level 
	 * parameter is overridden at the test level.
	 */
	@Parameters({ "suite-param", "test-three-param" })
	@Test
	public void prameterTestThree(String param, String paramTwo) {
		System.out.println("Test three suite param is: " + param);
		System.out.println("Test three param is: " + paramTwo);
	}
}

1.2 testng.xml

现在将testng.xml文件添加到项目根目录,并将以下代码放入其中。 在这里,我们定义要传递的参数值。

<suite name="Parameter test Suite" verbose="1">
	<!-- This parameter will be passed to every test in this suite -->
	<parameter name="suite-param" value="suite level parameter" />
	<test name="Parameter Test one">
		<classes>
			<class name="com.howtodoinjava.test.ParameterTest">
				<methods>
					<include name="prameterTestOne" />
				</methods>
			</class>
		</classes>
	</test>
	<test name="Parameter Test two">
		<!-- This parameter will be passed this test only -->
		<parameter name="test-two-param" value="Test two parameter" />
		<classes>
			<class name="com.howtodoinjava.test.ParameterTest">
				<methods>
					<include name="prameterTestTwo" />
				</methods>
			</class>
		</classes>
	</test>
	<test name="Parameter Test three">
		<!-- Overriding suite level parameter -->
		<parameter name="suite-param" value="overiding suite parameter" />
		<!-- Test specific parameter -->
		<parameter name="test-three-param" value="test three parameter" />
		<classes>
			<class name="com.howtodoinjava.test.ParameterTest">
				<methods>
					<include name="prameterTestThree" />
				</methods>
			</class>
		</classes>
	</test>
</suite>

1.3 演示

现在使用testng.xml运行以上测试。 以上测试运行的输出如下:

[TestNG] Running: C:\somepath\testng.xml

Test one suite param is: suite level parameter

Test two param is: Test two parameter

Test three suite param is: overiding suite parameter
Test three param is: test three parameter

===============================================
Parameter test Suite
Total tests run: 3, Failures: 0, Skips: 0
===============================================

从测试结果中可以看出,只有timeTestTwo()被执行,因为它的执行时间少于testng.xml文件中定义的超时时间。 timeTestOne()执行被取消,因为完成所需的时间超过配置的超时时间。

2. TestNG @Parameters – 可选参数

TestNG 还提供了提供可选参数的选项,如果在定义的文件中找不到参数值,则将使用此值。

2.1 使用@Optional注解进行测试

要传递可选参数,请使用@Optional注解。

package com.howtodoinjava.test;

import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class ParameterTest 
{
	@Parameters({ "optional-value" })
	@Test
	public void optionTest(@Optional("optional value") String value) {
		System.out.println("This is: " + value);
	}
}

前面的类文件包含一个将一个参数作为输入的测试方法。 所述执行中的测试方法打印使用System.out.println方法传递到控制台的参数值。

使用 XML 文件中名为optional-value的参数将参数值传递到测试方法。 使用针对所述参数的@Optional注解定义所述参数的可选值。

2.2 使用@Optional注解进行测试

在此testng.xml文件中,上面定义了两个测试。 在第一个测试中未定义任何参数,第二个测试在其中声明了一个名为“optional-value”的参数。

<suite name="Optional test Suite" verbose="1">

  <test name="Optional Test one">
    <classes>
      <class name="test.parameter.OptionalTest" />
    </classes>
  </test>

  <test name="Optional Test two">
    <parameter name="optional-value" value="passed from xml" />
    <classes>
      <class name="test.parameter.OptionalTest" />
    </classes>
  </test>

</suite>

2.3 演示

将以上代码作为测试套件运行的输出为:

[TestNG] Running: C:\somepath\testng.xml

This is: optional value
This is: passed from xml

===============================================
Optional test Suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================

从先前的测试结果可以看出,TestNG 在第一次执行测试时就将可选值传递给了测试方法。 发生这种情况是因为 TestNG 在第一次测试中无法在 XML 文件中找到名为optional-value的参数。

在第二次测试期间,它在 XML 中找到了参数值,并将该值传递给执行过程中的测试方法。

学习愉快!

TestNG @DataProvider – 测试参数示例

原文: https://howtodoinjava.com/testng/testng-dataprovider/

TestNG 提供的重要功能是 testng DataProvider功能。 它可以帮助您编写数据驱动的测试,这实际上意味着同一测试方法可以使用不同的数据集多次运行。

请注意,除了从testng.xml传递参数之外,@DataProvider是向测试方法传递参数的第二种方法。 它有助于为测试方法提供复杂的参数,因为不可能从 XML 做到这一点。

要在测试中使用DataProvider功能,您必须声明一个由@DataProvider注解的方法,然后在测试方法中使用该方法,并在测试方法中使用@Test注解。

1. TestNG 同一类中的@DataProvider@Test

下面的测试类包含一个测试方法,该方法采用一个参数作为输入,并在执行时将其打印到控制台。 通过使用 TestNG 的@DataProvider注解,在同一类中也可以使用DataProvider方法。

使用@DataProvider注解的name属性提及所述DataProvider方法的名称。 DataProvider返回一个具有两个数据集的Object类双数组,即“数据一”和“数据二”。

package com.howtodoinjava.test;

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class SameClassDataProvider 
{
	@DataProvider(name = "data-provider")
	public Object[][] dataProviderMethod() {
		return new Object[][] { { "data one" }, { "data two" } };
	}

	@Test(dataProvider = "data-provider")
	public void testMethod(String data) {
		System.out.println("Data is: " + data);
	}
}

现在运行以上测试。 以上测试运行的输出如下:

Data is: data one
Data is: data two

PASSED: testMethod("data one")
PASSED: testMethod("data two")

从上面的测试结果可以看出,该类中的相应测试方法已执行了两次。 测试方法的执行取决于DataProvider方法传递的数据集的数量,在这种情况下,由于DataProvider返回了两组不同的数据,因此测试方法执行了两次。

@DataProvider方法必须以Object类的双数组(Object[][])形式返回数据。 第一个数组代表一个数据集,其中第二个数组包含参数值。

2. TestNG 不同类中的@DataProvider@Test

要了解这一点,请添加两个名称分别为DataProviderClassTestClass的类,如下所示。

2.1. DataProvider.java

import org.testng.annotations.DataProvider;

public class DataProviderClass 
{
	@DataProvider(name = "data-provider")
	public static Object[][] dataProviderMethod() 
	{
		return new Object[][] { { "data one" }, { "data two" } };
	}
}

2.2. TestClass.java

import org.testng.annotations.Test;

public class TestClass 
{
	@Test(dataProvider = "data-provider", dataProviderClass = DataProviderClass.class)
	public void testMethod(String data) 
	{
		System.out.println("Data is: " + data);
	}
}

2.3 演示

现在运行以上测试。 以上测试运行的输出如下:

Data is: data one
Data is: data two

PASSED: testMethod("data one")
PASSED: testMethod("data two")

从上面的测试结果中可以看到,根据DataProvider方法传递给它的数据,该测试方法已执行两次。 在这种情况下,DataProvider方法位于另一个类中。 在这种情况下,必须将dataProviderMethod()声明为静态,以便测试方法可以使用另一类中的dataProviderMethod()来提供数据。

学习愉快!

TestNG @Factory注解教程

原文: https://howtodoinjava.com/testng/testng-factory-annotation-tutorial/

让我们了解 TestNG @Factory注解,该注解允许根据某些数据集或条件在运行时创建测试。

Table of Contents

1\. When to use testng factory
2\. Basic testng factory example
3\. TestNG factory - test parameters
4\. TestNG @Factory with @DataProvider
5\. TestNG @Factory - dependent tests

1. 何时使用 testng 工厂

有时我们可能需要使用不同的数据值运行一组测试。 为此,我们可以在 testng XML 的套件中定义一组独立的测试,并测试所需的场景。 这种方法的问题在于,如果您获得了额外的数据集,则需要重新定义测试。

TestNG 通过提供@Factory注解功能解决了此问题。 TestNG 中的工厂在运行时动态定义和创建测试。

工厂方法必须返回Object类的数组,即Object[]

2. 基本的 testng 工厂示例

让我们使用 TestNG 的@Factory注解创建一个示例程序,以了解如何使用工厂

public class SimpleTest 
{
	@Test
	public void simpleTest() {
		System.out.println("Simple Test Method.");
	}
}

public class SimpleTestFactory 
{
	@Factory
	public Object[] factoryMethod() {
		return new Object[] { new SimpleTest(), new SimpleTest() };
	}
}

上一类在其中定义了工厂方法。 通过在相应的测试方法上方声明@Factory来定义工厂方法。

让我们现在运行工厂。

Simple Test Method.
Simple Test Method.
PASSED: simpleTest
PASSED: simpleTest

如您在前面的测试结果中看到的,SimpleTestFactory类的测试方法执行了两次。 该执行基于工厂方法返回的Object数组。 当所述方法返回SimpleTest类的两个对象时,TestNG 在指定的返回对象内部查找并在其中执行所有测试方法。 在这种情况下,由于只有一种测试方法,因此 TestNG 会执行相应的测试方法。

3. TestNG 工厂参数

使用工厂方法的主要优点之一是,您可以在初始化它们时将参数传递给测试类。 然后,可以在上述类中存在的所有测试方法中使用这些参数。

public class SimpleTest 
{
	private int param;

	public SimpleTest(int param) {
		this.param = param;
	}

	@Test
	public void testMethodOne() {
		int opValue = param + 1;
		System.out.println("Test method one output: " + opValue);
	}

	@Test
	public void testMethodTwo() {
		int opValue = param + 2;
		System.out.println("Test method two output: " + opValue);
	}
}

public class SimpleTestFactory 
{
	@Factory
	public Object[] factoryMethod() {
		return new Object[] { new SimpleTest(0), new SimpleTest(1) };
	}
}

上一个测试类的构造器将一个参数作为整数,并将其分配给局部变量param。 然后,在测试类中提供的两种测试方法中使用此变量。 每个测试方法都会为param添加一个值,并在执行时将其打印到控制台。

现在开始运行测试。

Test method one output: 2
Test method one output: 1
Test method two output: 3
Test method two output: 2

PASSED: testMethodOne
PASSED: testMethodOne
PASSED: testMethodTwo
PASSED: testMethodTwo

从前面的测试结果中可以看到,每个测试方法分别执行两次。 测试方法使用初始化测试类时传递的参数,并且控制台显示相应的输出。

4. TestNG @Factory@DataProvider

@DataProvider功能也可以与@Factory注解一起使用,以在运行时创建测试。 这可以通过在类的构造器或常规方法上声明@Factory注解来完成。

public class DataProviderTest 
{
	private int param;

	@Factory(dataProvider = "dataMethod")
	public DataProviderTest(int param) {
		this.param = param;
	}

	@DataProvider
	public static Object[][] dataMethod() {
		return new Object[][] { { 0 }, { 1 } };
	}

	@Test
	public void testMethodOne() {
		int opValue = param + 1;
		System.out.println("Test method one output: " + opValue);
	}

	@Test
	public void testMethodTwo() {
		int opValue = param + 2;
		System.out.println("Test method two output: " + opValue);
	}
}

前面的类类似于我们先前使用的测试类。 测试类的构造器带有@Factory注解。 此注解使用名为dataMethodDataProvider方法为测试类的构造器提供值。 DataProvider方法返回一个双对象数组,其中第一个数组代表数据集,该数据集确定要迭代测试的次数,而第二个数组是每次迭代将传递给测试方法的实际参数值。 所述双对象数组包含两个数据集,其值分别为 0 和 1。

现在开始运行测试。

Test method one output: 2
Test method one output: 1
Test method two output: 3
Test method two output: 2

PASSED: testMethodOne
PASSED: testMethodOne
PASSED: testMethodTwo
PASSED: testMethodTwo

5. TestNG @Factory – 依赖测试

到目前为止,我们已经看到了工厂实现的不同示例。 让我们看看与工厂类一起使用时如何执行依赖方法。

public class DependencyTest 
{
	private int param;

	public DependencyTest(int param) {
		this.param = param;
	}

	@Test(dependsOnMethods = { "testMethodTwo" })
	public void testMethodOne() {
		System.out.println("Test method one with param values: " + this.param);
	}

	@Test
	public void testMethodTwo() {
		System.out.println("Test method two with param values: " + this.param);
	}
}

public class SimpleTestFactory 
{
	@Factory
	public Object[] factoryMethod() 
	{
		return new Object[] { new DependencyTest(1), new DependencyTest(2) };
	}
}

此类包含两种测试方法testMethodOnetestMethodTwo,其中testMethodOne取决于testMethodTwo。 该类的构造器将一个参数作为整数,并将其值设置为名为param的内部变量。 这两种测试方法在执行时都会将其方法名称和param变量值一起打印到控制台。

现在开始运行测试。

Test method two with param values: 2
Test method two with param values: 1
Test method one with param values: 2
Test method one with param values: 1

PASSED: testMethodTwo
PASSED: testMethodTwo
PASSED: testMethodOne
PASSED: testMethodOne

从前面的测试结果可以看出,testMethodTwo的两个实例都在testMethodOne的任何实例之前执行。 这是 TestNG 中工厂实现的默认行为,它将在实际测试方法之前执行从属测试方法的所有实例。

这与 TestNG 中的 @Factory注解有关。 如果您有任何疑问,请告诉我。

学习愉快!

参考:

TestNG 文档

TestNG – @Factory@DataProvider之间的区别

原文: https://howtodoinjava.com/testng/testng-difference-between-factory-and-dataprovider/

在学习 TestNG 时,许多人在阅读@DataProvider@Factory注解时会感到困惑 - 什么时候使用什么? 还有什么更好的呢? 让我们看看它们的两个功能。

@Factory@DataProvider之间的区别

以下是 TestNG 上@Factory@DataProvider功能之间的主要区别

  1. 数据供应器:使用数据供应器的测试方法将根据数据供应器提供的数据执行多次。 该测试方法将使用该测试方法所属的相同测试类实例来执行。
  2. 工厂:工厂将使用相应类的单独实例执行测试类中存在的所有测试方法。

TestNG 工厂用于动态创建测试类的实例。 如果要多次运行测试类,这将很有用。 例如,如果您有一个要登录到站点的测试并且想要多次运行该测试,则它易于使用的 TestNG 工厂在其中创建多个测试类实例并运行测试(可能是测试任何内存泄漏) 问题)。

而数据供应器用于为测试提供参数。 如果为测试提供数据供应器,则每次运行测试时将使用不同的值集。 对于需要每次使用不同用户名和密码集登录到站点的情况,这很有用。

让我们创建一个示例,展示这两者之间的明显区别。

@DataProvider示例

下列类包含testMethodbeforeClass方法。 testMethod接受String参数,该参数的值由DataProvider方法dataMethod提供。 beforeClass方法在执行时将消息打印到控制台上,testMethod也是如此。 testMethod在执行时将传递给它的参数打印到控制台。

public class DataProviderClass 
{
	@BeforeClass
	public void beforeClass() {
		System.out.println("Before class executed");
	}

	@Test(dataProvider = "dataMethod")
	public void testMethod(String param) {
		System.out.println("The parameter value is: " + param);
	}

	@DataProvider
	public Object[][] dataMethod() {
		return new Object[][] { { "one" }, { "two" } };
	}
}

让我们进行测试。

Before class executed
The parameter value is: one
The parameter value is: two
PASSED: testMethod("one")
PASSED: testMethod("two")

从前面的测试结果中可以看到,无论执行了多少次测试方法,类beforeClass仅执行一次。

@Factory示例

下列类包含testMethodbeforeClass方法。 测试类的构造器采用String参数值。 beforeClasstestMethod都将消息打印到控制台上。

public class SimpleTest 
{
	private String param = "";

	public SimpleTest(String param) {
		this.param = param;
	}

	@BeforeClass
	public void beforeClass() {
		System.out.println("Before SimpleTest class executed.");
	}

	@Test
	public void testMethod() {
		System.out.println("testMethod parameter value is: " + param);
	}
}

public class SimpleTestFactory 
{
	@Factory
	public Object[] factoryMethod() {
		return new Object[] { 
								new SimpleTest("one"), 
								new SimpleTest("two") 
							};
	}
}

让我们进行测试。

Before SimpleTest class executed.
testMethod parameter value is: two
Before SimpleTest class executed.
testMethod parameter value is: one
PASSED: testMethod
PASSED: testMethod

从先前的测试结果可以看出,在每次执行testMethod之前,都会执行beforeClass方法。 这表明工厂实现对测试类的每个单独实例执行测试方法。 如前所述,DataProvider为测试类的单个实例执行测试方法(testMethod)。

祝您学习愉快!

TestNG 的前后注解

原文: https://howtodoinjava.com/testng/testng-before-and-after-annotations/

TestNG 的前后注解主要用于在执行测试方法之前和之后执行一组特定的代码。 这些用于基本上在测试执行开始之前设置一些变量或配置,然后在测试执行结束之后清除所有这些内容。

1. TestNG 的前后注解

TestNG 提供了五种不同的BeforeAfter注解选项,可以根据测试要求使用每种注解选项。 以下是 TestNG 提供的不同之前和之后选项。

注解 描述
@BeforeSuite 带注解的方法将在该套件中的所有测试运行之前运行。
@BeforeTest 带注解的方法将在运行属于test标记内的类的任何测试方法之前运行。
@BeforeGroups 此配置方法将在其之前运行的组的列表。 保证此方法可以在调用属于任何一个组的第一个测试方法之前不久运行。
@BeforeClass 带注解的方法将在调用当前类中的第一个测试方法之前运行。
@BeforeMethod 带注解的方法将在当前类中的所有测试方法运行之前运行。
@AfterSuite 带注解的方法将在该套件中的所有测试运行之后运行。
@AfterTest 带注解的方法将在所有属于test标记内的类的测试方法运行后运行。
@AfterGroups 此配置方法将在其后运行的组的列表。 保证在调用属于这些组中任何一个的最后一个测试方法后不久便可以运行该方法。
@AfterClass 带注解的方法将在当前类中的所有测试方法运行之后运行。
@AfterMethod 带注解的方法将在每种测试方法之后运行。

让我们尝试一个包含所有前面带注解的方法的示例,并了解它们何时执行。

2. TestNG 前后注解的示例

创建一个具有所有前后注解的新 TestNG 测试。 您可以根据此 TestNG 教程中给出的说明来创建此测试。 让我们看看如何选择所有之前和之后的注解。

Select all testng annotations

单击确定后,您将获得带有所有注解的测试。 在所有方法中添加一些打印语句,以便可以按执行顺序对其进行跟踪。

package com.howtodoinjava.test;

import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

public class MyFirstTest 
{
	@Test
	public void testCase() {
	}

	@BeforeSuite
	public void beforeSuite() {
		System.out.println("Before Suite method");
	}

	@AfterSuite
	public void afterSuite() {
		System.out.println("After Suite method");
	}

	@BeforeTest
	public void beforeTest() {
		System.out.println("Before Test method");
	}

	@AfterTest
	public void afterTest() {
		System.out.println("After Test method");
	}

	@BeforeClass
	public void beforeClass() {
		System.out.println("Before Class method");
	}

	@AfterClass
	public void afterClass() {
		System.out.println("After Class method");
	}

	@BeforeMethod
	public void beforeMethod() {
		System.out.println("Before Method");
	}

	@AfterMethod
	public void afterMethod() {
		System.out.println("After Method");
	}
}

现在在测试用例上运行 TestNG 测试,您将在控制台中获得以下输出。

[TestNG] Running:
  C:\Users\somepath\testng-customsuite.xml

Before Suite method
Before Test method
Before Class method
Before Method
After Method
After Class method
After Test method
PASSED: testCase

===============================================
    Default test
    Tests run: 1, Failures: 0, Skips: 0
===============================================

After Suite method

===============================================
Default suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

[TestNG] Time taken by org.testng.reporters.XMLReporter@177b3cd: 19 ms
[TestNG] Time taken by [FailedReporter passed=0 failed=0 skipped=0]: 0 ms
[TestNG] Time taken by org.testng.reporters.jq.Main@b8deef: 53 ms
[TestNG] Time taken by org.testng.reporters.JUnitReportReporter@10ab323: 13 ms
[TestNG] Time taken by org.testng.reporters.EmailableReporter2@5e176f: 11 ms
[TestNG] Time taken by org.testng.reporters.SuiteHTMLReporter@d1e89e: 184 ms

恭喜,您已经成功创建了具有各种前后注解的测试类,并执行了该类。

当前示例仅包含存在于同一类中的注解。 当包含注解的类被另一个具有前后一组注解的类扩展时,让我们学习执行流程。

3. 在超类上放置之前和之后注解

让我们创建两个新类BaseClassChildClass。 然后在两者上添加类似的前后注解。 这里主要要注意的是ChildClass extends BaseClass。 测试在ChildClass类中定义。

3.1 父类

package com.howtodoinjava.test;

import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;

public class BaseClass {

	@BeforeMethod
	public void beforeMethod() {
		System.out.println("BaseClass's Before Test method");
	}

	@AfterMethod
	public void afterMethod() {
		System.out.println("BaseClass's After Test method");
	}

	@BeforeClass
	public void beforeClass() {
		System.out.println("BaseClass's Before Class method");
	}

	@AfterClass
	public void afterClass() {
		System.out.println("BaseClass's After Class method");
	}
}

2. 子类

package com.howtodoinjava.test;

import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class ChildClass extends BaseClass {

	@BeforeMethod
	public void beforeChildMethod() {
		System.out.println("ChildClass's Before Test method");
	}

	@AfterMethod
	public void afterChildMethod() {
		System.out.println("ChildClass's After Test method");
	}

	@BeforeClass
	public void beforeChildClass() {
		System.out.println("ChildClass's Before Class method");
	}

	@AfterClass
	public void afterChildClass() {
		System.out.println("ChildClass's After Class method");
	}

	@Test
	public void testCase() {
		System.out.println("===== Executing actual test ======");
	}
}

执行ChildClass测试将生成以下输出。

[TestNG] Running:
  C:\Users\somepath\testng-customsuite.xml

BaseClass's Before Class method
ChildClass's Before Class method
BaseClass's Before Test method
ChildClass's Before Test method
===== Executing actual test ======
ChildClass's After Test method
BaseClass's After Test method
ChildClass's After Class method
BaseClass's After Class method
PASSED: testCase

===============================================
    Default test
    Tests run: 1, Failures: 0, Skips: 0
===============================================

===============================================
Default suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

[TestNG] Time taken by org.testng.reporters.EmailableReporter2@1549f94: 13 ms
[TestNG] Time taken by [FailedReporter passed=0 failed=0 skipped=0]: 0 ms
[TestNG] Time taken by org.testng.reporters.XMLReporter@1bd7848: 16 ms
[TestNG] Time taken by org.testng.reporters.jq.Main@1342ba4: 52 ms
[TestNG] Time taken by org.testng.reporters.JUnitReportReporter@176e552: 12 ms
[TestNG] Time taken by org.testng.reporters.SuiteHTMLReporter@ff057f: 190 ms

如您所见,TestNG 的报告输出在被注解的方法之前执行父类,然后在被注解的方法之前执行子类。 在带注解的方法之后,先执行子类方法,然后再执行父类。

这有助于我们在所有测试类中使用通用的注解前方法,并在需要时为每个测试类使用特定的前/后注解方法

如果您有任何问题要给我留言。

学习愉快!

参考:

TestNG 文档[​​HTG1]

TestNG – 测试组,元组,默认组示例

原文: https://howtodoinjava.com/testng/testng-test-groups-meta-group-default-group-examples/

分组测试方法是 TestNG 的最重要功能之一。 在 TestNG 中,用户可以将多种测试方法分组为一个命名组。 您还可以执行属于一个或多个组的一组特定的测试方法。 此功能允许将测试方法分为不同的部分或模块。 例如,您可以拥有一组属于健全性测试的测试,而其他测试则可能属于回归测试。 您还可以根据测试方法验证的功能/特征分离测试。 这有助于在需要时仅执行一组特定的测试。

在本教程中,我们将在以下步骤/部分中了解有关 TestNG 中测试分组的信息。

Table of Contents

Grouping tests example
Running a TestNG group through Eclipse
Running a TestNG group through testng.xml
Writing tests which belong to multiple groups
Including and excluding groups
Using regular expressions
Default group
Group of groups

分组测试示例

让我们创建一个测试类,其中包含属于某个组的某些测试方法。

package com.howtodoinjava.groupExamples;

import org.testng.annotations.Test;

public class TestGroupExample 
{
	@Test(groups = { "test-group" })
	public void testMethodOne() {
		System.out.println("Test method one belonging to group.");
	}

	@Test
	public void testMethodTwo() {
		System.out.println("Test method two not belonging to group.");
	}

	@Test(groups = { "test-group" })
	public void testMethodThree() {
		System.out.println("Test method three belonging to group.");
	}
}

如果您将在 eclipse 中正常运行上述测试,则​​测试执行将不考虑要执行的组,因此将执行指定测试类中的所有测试。 如果只想在某个特定组下执行方法,则将以以下两节中讨论的任一种方式执行它们。

通过 Eclipse 运行 TestNG 组

在前面的部分中,我们创建了一个测试类,其中包含属于测试组的某些测试方法。 现在,让我们使用 Eclipse 运行测试组。

1)转到“运行 | 运行配置”

2)从可用配置列表中选择 TestNG,然后单击“新的配置”图标

3)在新的配置窗口中,提供配置名称,例如TestGroupExample

4)转到项目部分,然后单击“浏览”按钮。 选择先前创建的项目,即TestNGExamples

Select group in eclipse

5)转到“分组”部分,然后单击“浏览”按钮。 从列表中选择要执行的组,在这种情况下为测试组。

Browse Group Name

6)单击“应用”按钮,然后单击“运行”。 以下结果将显示在 Eclipse 的 TestNG 的“结果”窗口中:

Test method one belonging to group.
Test method three belonging to group.
PASSED: testMethodOne
PASSED: testMethodThree

===============================================
    GRP-test-group
    Tests run: 2, Failures: 0, Skips: 0
===============================================

恭喜,您已经使用 Eclipse 中的 TestNG 运行器配置成功执行了属于特定组的测试方法。 您还可以通过在“浏览”部分中选择相应的组来使用该工具执行多个组。 通常,最好使用基于 TestNG-XML 的执行来执行属于特定组的测试方法。

通过testng.xml运行 TestNG 组

现在让我们学习如何创建一个 testng XML 文件来执行属于特定组的测试方法。 此方法是执行组的首选且简便的方法。 而且,这些 testng XML 文件然后可以与构建工具一起使用以执行 TestNG 测试套件。

1)打开 Eclipse 并在先前创建的项目中创建一个名称为testng.xml的新文件。

2)在上述文件中添加以下代码:

<suite name="Time test Suite" verbose="1">
  <test name="Group Test">
    <groups>
      <run>
        <include name="test-group" />
      </run>
    </groups>
    <classes>
      <class name="com.howtodoinjava.groupExamples.TestGroupExample" />
    </classes>
  </test>
</suite>

该 xml 文件在套件中仅包含一个测试。 它包含通过使用groups标记定义的groups部分,如代码所示。 运行标签表示需要运行的组。 include标记代表需要执行的组的名称。

3)选择先前创建的 testng XML 文件并将其作为 TestNG 套件运行。 您将看到以下测试结果:

Test method one belonging to group.
Test method three belonging to group.

===============================================
Time test Suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================

很棒。 我们成功创建了一个 testng XML 文件,该文件中包含一个组,从而在上述套件中创建了一个测试。 这是通过将上述组包含在运行部分中来完成的。 运行部分又是测试内部groups标签部分的一部分。 TestNG 将在测试的类部分中提到的类下查找属于该组的测试方法。 用户还可以提供测试包。 TestNG 将搜索添加到测试中的所有类,以包括或排除属于特定组的特定测试方法。 一旦找到,这些测试方法将由 TestNG 作为测试套件执行。

编写属于多个组的测试

之前我们了解了创建属于单个组的测试的方法,但是 TestNG 允许测试方法也属于多个组。 这可以通过在@Test注解的groups属性中以数组形式提供组名称来完成。 让我们创建一个包含多个小组的示例程序,以了解其操作方法。

package com.howtodoinjava.groupExamples;

import org.testng.annotations.Test;

public class MultiGroupExample 
{
	@Test(groups = { "group-one" })
	public void testMethodOne() {
		System.out.println("Test method one belonging to group.");
	}

	@Test(groups = { "group-one", "group-two" })
	public void testMethodTwo() {
		System.out.println("Test method two belonging to both group.");
	}

	@Test(groups = { "group-two" })
	public void testMethodThree() {
		System.out.println("Test method three belonging to group.");
	}
}

上一类包含三种测试方法。 其中两种测试方法分别属于一组,其中一种方法属于两组,分别是第一组和第二组。

现在,编辑testng.xml文件,如下所示:

<suite name="Multi Group Suite" verbose="1">
  <test name="Group Test one">
    <groups>
      <run>
        <include name="group-one" />
      </run>
    </groups>
    <classes>
      <class name="com.howtodoinjava.groupExamples.MultiGroupExample" />
    </classes>
  </test>
  <test name="Group Test two">
    <groups>
      <run>
        <include name="group-two" />
      </run>
    </groups>
    <classes>
      <class name="com.howtodoinjava.groupExamples.MultiGroupExample" />
    </classes>
  </test>
</suite>

前面的 testng XML 套件包含两个测试,每个测试都执行属于特定组的测试方法。 选择 testng XML 文件并将其作为 TestNG 套件运行。 您将看到以下测试结果:

Test method one belonging to group.
Test method two belonging to both group.

Test method three belonging to group.
Test method two belonging to both group.

===============================================
Multi Group Suite
Total tests run: 4, Failures: 0, Skips: 0
===============================================

在这里,我们成功创建了一个测试方法,该方法属于多个组并且可以成功执行。 如您在先前的测试结果中看到的,在测试套件的两个测试中都执行了testMethodTwo()。 这是因为它属于两个由 TestNG 执行测试方法的组。

包括和排除组

TestNG 还允许您从测试执行中包括和排除某些组。 这有助于仅执行一组特定的测试,而排除某些测试。 一个简单的例子是某个功能损坏时,您需要从执行中排除一组固定的测试,因为这些测试将在执行时失败。 修复功能之后,您可以通过执行相应的测试组来验证功能。

让我们创建一个示例程序,并学习如何排除一组测试。

package com.howtodoinjava.groupExamples;

import org.testng.annotations.Test;

public class ExcludeGroupTest 
{
	@Test(groups = { "include-group" })
	public void testMethodOne() {
		System.out.println("Test method one belonging to include group.");
	}

	@Test(groups = { "include-group" })
	public void testMethodTwo() {
		System.out.println("Test method two belonging to include group.");
	}

	@Test(groups = { "include-group", "exclude-group" })
	public void testMethodThree() {
		System.out.println("Test method three belonging to exclude/include groups.");
	}
}

上一类包含三种测试方法,这些方法在执行时将消息打印到控制台上。 这三种方法都属于组include-group,而testMethodThree()方法也属于组exclude-group

<suite name="Exclude Group Suite" verbose="1">
  <test name="Exclude Group Test">
    <groups>
      <run>
        <include name="include-group" />
        <exclude name="exclude-group" />
      </run>
    </groups>
    <classes>
      <class name="com.howtodoinjava.groupExamples.ExcludeGroupTest" />
    </classes>
  </test>
</suite>

现在在testng.xml文件上方运行,它将产生以下结果。

Test method one belonging to include group.
Test method two belonging to include group.

===============================================
Exclude Group Suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================

从先前的测试结果中可以看出,TestNG 执行了include-group中的两种方法,并排除了属于exclude-group的第三种方法,它从测试执行中排除。

如果测试方法既属于包含组又属于排除组,则排除组具有优先权,并且该测试方法将从测试执行中排除。

使用正则表达式

在将测试配置为包含或排除组时,TestNG 允许用户使用正则表达式。 这类似于包含和排除我们前面介绍的测试方法。 这可以帮助用户根据名称搜索包含和排除组。

让我们学习如何排除基于基于正则表达式的名称匹配的测试。

package com.howtodoinjava.groupExamples;

import org.testng.annotations.Test;

public class RegularExpressionGroupTest
{
	@Test(groups = { "include-test-one" })
	public void testMethodOne() {
		System.out.println("Test method one");
	}

	@Test(groups = { "include-test-two" })
	public void testMethodTwo() {
		System.out.println("Test method two");
	}

	@Test(groups = { "test-one-exclude" })
	public void testMethodThree() {
		System.out.println("Test method three");
	}

	@Test(groups = { "test-two-exclude" })
	public void testMethodFour() {
		System.out.println("Test method Four");
	}
}

testng.xml文件。

<suite name="Regular Exp. Group Suite" verbose="1">
  <test name="Regular Exp. Test">
    <groups>
      <run>
        <include name="include.*" />
        <exclude name=".*exclude" />
      </run>
    </groups>
    <classes>
      <class name="com.howtodoinjava.groupExamples.RegularExpressionGroupTest" />
    </classes>
  </test>
</suite>

前面的 XML 包含一个简单的测试,其中包含名称以include开头的所有组,而名称以exclude结尾的所有组都从测试执行中排除。

现在运行testng.xml文件,您将在控制台中获得以下结果。

Test method one
Test method two

===============================================
Regular Exp. Group Suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================

在此,TestNG 执行了两个名称以include开头的组的方法,并排除了名称以exclude结尾的组的测试方法。

要使用正则表达式包含和排除组,必须使用.*来匹配名称。 通过在搜索字符串的开头和结尾使用表达式(例如,.*name.*),我们也可以将其用于搜索名称中包含某个字符串的组。

分配默认组

有时我们可能需要将默认组分配给属于一个类的一组测试方法。 这样,属于所述类的所有公共方法将自动成为 TestNG 测试方法,并成为所述组的一部分。

这可以通过在类级别使用@Test注解并在所述@Test注解中定义默认组来实现。

@Test(groups={"default-group"})
public class DefaultGroup {
  public void testMethodOne(){
    System.out.println("Test method one.");
  }

  public void testMethodTwo(){
    System.out.println("Test method two.");
  }

  @Test(groups={"test-group"})
  public void testMethodThree(){
    System.out.println("Test method three.");
  }
}  

组中组或“元组”

TestNG 允许用户从现有组中创建组,然后在创建测试套件时使用它们。 您可以通过包含和排除某些组来创建新组,然后使用它们。

让我们创建一个示例测试程序,并学习如何创建称为元组的组中组。

package com.howtodoinjava.groupExamples;

import org.testng.annotations.Test;

public class RegularExpressionGroupTest
{
	@Test(groups = { "include-test-one" })
	public void testMethodOne() {
		System.out.println("Test method one");
	}

	@Test(groups = { "include-test-two" })
	public void testMethodTwo() {
		System.out.println("Test method two");
	}

	@Test(groups = { "test-one-exclude" })
	public void testMethodThree() {
		System.out.println("Test method three");
	}

	@Test(groups = { "test-two-exclude" })
	public void testMethodFour() {
		System.out.println("Test method Four");
	}
}

现在创建testng.xml文件,如下所示:

<suite name="Group of group Suite" verbose="1">
  <test name="Group of group Test">
    <groups>
      <define name="include-group">
        <include name="include-test-one" />
        <include name="include-test-two" />
      </define>
      <define name="exclude-group">
        <include name="test-one-exclude" />
        <include name="test-two-exclude" />
      </define>
      <run>
        <include name="include-group" />
        <exclude name="exclude-group" />
      </run>
    </groups>
    <classes>
      <class name="com.howtodoinjava.groupExamples.RegularExpressionGroupTest" />
    </classes>
  </test>
</suite>

这里在测试内部定义了两组,然后将这些组用于测试执行。 使用groups标签内的define标签创建元组。 使用define标签下的name属性定义新组的名称。 通过使用includeexclude标签从新组中排除组。

现在运行testng.xml测试,它将在控制台中产生以下结果:

Test method one
Test method two

===============================================
Group of group Suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================

在这里,testNG 仅执行两种方法,如在include-group组中提到的,并排除了属于exclude-group的测试方法。 您可以根据需要定义任意多个组。

此功能有助于为回归,健全性和模块测试创建特定的组。

这些都与 TestNG 中的测试组有关。 让我知道您是否有任何疑问。

学习愉快!

Mockito 教程

Mockito2 教程 – JUnit Mockito 示例

原文: https://howtodoinjava.com/mockito/junit-mockito-example/

在本 mockito 教程中,学习了 Mockito 框架的基础知识,如何编写 junit 测试以及模拟,样例设置和带有示例的注解。

Table of Contents

1\. Introduction
2\. Setting Up
3\. Annotations
4\. Example

1. Mockito 简介

在应用的单元测试期间,有时无法复制精确的生产环境。 有时数据库不可用,有时不允许网络访问。 可以有更多这样的限制。 为了解决这些限制,我们必须为这些不可用的资源创建模拟。

Mockito 是一个开放源代码框架,可让您轻松创建测试替身(模拟)。 对于您出于测试目的而替换生产对象的任何情况,测试替身是一个通用术语。

在模拟中,我们通常使用以下类型的测试替身。

  • 存根 – 是一个对象,该对象对测试期间执行的方法执行具有预定义的返回值。
  • 间谍 – 与存根相似的对象,但它们另外记录了它们的执行方式。
  • 模拟 – 是在测试期间对方法执行返回值并记录了对这些执行的期望的对象。 如果模拟人收到了他们不期望的电话,并且在验证过程中对其进行了检查,以确保他们收到了所有期望的电话,它们可能会引发异常。

我们可以在测试类中模拟接口和类。 Mockito 还可以在使用 Mockito 注解时帮助产生最少的样板代码。

2. Mockito 设置

要将 Mockito 添加到项目中,我们可以通过任何方式添加所需的 Mockito 版本,即 maven ,gradle 或 jar 文件

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.23.4</version>
    <scope>test</scope>
</dependency>

testCompile group: 'org.mockito', name: 'mockito-core', version: '2.23.4'

下面给出了具有创建 Junit 测试以及 mockito 的所有依赖项的完整 pom 文件。 否则,我们需要查找并添加objenesishamcrest-corebyte-buddybyte-buddy-agentjunitmockito的匹配版本。

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.howtodoinjava.demo</groupId>
	<artifactId>MockitoExample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<dependencies>
		<dependency>
			<groupId>org.mockito</groupId>
			<artifactId>mockito-core</artifactId>
			<version>2.23.0</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId> 
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

3. Mockito 注解

在敲键盘来编写应用和单元测试之前,让我们快速概述有用的 Mockito 注解。

  • @Mock用于模拟创建。 它使测试类更具可读性。
  • @Spy用于创建间谍实例。 我们可以使用它代替spy(Object)方法。
  • @InjectMock用于自动实例化测试对象,并将所有@Mock@Spy带注解的字段依赖项注入其中(如果适用)。
  • @Captor用于创建参数捕获器

阅读更多: @Mock@InitMock注解之间的区别

要处理所有上述注解,MockitoAnnotations.initMock(testClass);必须至少使用一次。 要处理注解,我们可以使用内置运行器MockitoJUnitRunner或规则MockitoRule。 我们还可以在@Before带注解的 Junit 方法中显式调用initMock()方法。

//1

@RunWith(MockitoJUnitRunner.class)
public class ApplicationTest {
	//code
}

//2

public class ApplicationTest {
	@Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_Stub);

	//code
}

//3

public class ApplicationTest {
	@Before
	public void init() {
		MockitoAnnotations.initMock(this);
	}
}

4. JUnit Mockito 示例

让我们学习编写将模拟程序用于依赖项的 junit 测试。 给定的示例具有RecordService,该文件在DatabaseDAONetworkDAO的帮助下将给定文件存储在数据库和网络位置中。

在测试环境中,无法访问数据库或网络位置,因此我们正在为两个仓库创建模拟。

public class DatabaseDAO 
{
	public void save(String fileName) {
		System.out.println("Saved in database");
	}
}

public class NetworkDAO 
{
	public void save(String fileName) {
		System.out.println("Saved in network location");
	}
}

public class RecordService 
{
	DatabaseDAO database;
	NetworkDAO network;

	//setters and getters

	public boolean save(String fileName) 
	{
		database.save(fileName);
		System.out.println("Saved in database in Main class");

		network.save(fileName);
		System.out.println("Saved in network in Main class");

		return true;
	}
}

请记住,如果我们使用诸如 Spring 之类的任何 DI 框架,那么我们可能已经使用了注解@Autowired

要测试这个RecordService类,让我们创建一个单元测试。

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMock;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import com.howtodoinjava.demo.mockito.DatabaseDAO;
import com.howtodoinjava.demo.mockito.NetworkDAO;
import com.howtodoinjava.demo.mockito.RecordService;

@RunWith(MockitoJUnitRunner.class)
public class ApplicationTest 
{
	@InjectMock
	RecordService recordService;

	@Mock
	DatabaseDAO databaseMock;

	@Mock
	NetworkDAO networkMock;

	@Test
	public void saveTest()
	{
		boolean saved = recordService.save("temp.txt");
		assertEquals(true, saved);

		verify(databaseMock, times(1)).save("temp.txt");
		verify(networkMock, times(1)).save("temp.txt");
	}
}

让我们逐步了解以上测试类:

  • @RunWith(MockitoJUnitRunner.class)注解测试,以便 Mockito 可以处理注解。
  • 使用@Mock注解对 dao 字段进行注解,以为它们两个实例化一个模拟对象。
  • 使用@InjectMock注解对服务字段进行注解,以首先实例化然后注入两个模拟的依赖项。
  • 在要测试的类上调用方法进行测试,即recordService
  • 验证模拟对象中的方法已被调用。

还有许多其他方法可以测试方法和模拟的依赖项,我们将在以后的文章中介绍。

将有关此 junit Mockito2 示例的问题交给我。

学习愉快!

参考: Mockito Core Java 文档

Mockito 注解– @Mock@Spy@Captor@InjectMock

原文: https://howtodoinjava.com/mockito/mockito-annotations/

在此 Mockito 教程中,了解 Mockito 注解,例如@Mock@Spy@Captor@InjectMock。 学习使用模仿注解为行为测试编写单元测试。

1. Mockito 注解

1.1 @Mock

@Mock注解用于创建和注入模拟实例。 我们不创建真实的对象,而是要求模拟为类创建一个模拟。

@Mock注解是Mockito.mock(classToMock)的替代方法。 它们都达到相同的结果。 通常认为使用@Mock是“清洁器”,因为我们没有用看起来都一样的样板分配填充测试。

使用@Mock注解:

  • 允许快速创建测试所需的对象。
  • 最小化重复的模拟创建代码。
  • 使测试类更具可读性。
  • 由于使用字段名称来识别模拟,因此使验证错误更易于阅读。

在给定的示例中,我们模拟了HashMap类。 在实际测试中,我们将模拟实际的应用类。 我们在映射中放置了一个键值对,然后验证了对模拟映射实例执行了方法调用。

@Mock
HashMap<String, Integer> mockHashMap;

@Test
public void saveTest()
{
	mockHashMap.put("A", 1);

	Mockito.verify(mockHashMap, times(1)).put("A", 1);
	Mockito.verify(mockHashMap, times(0)).get("A");

	assertEquals(0, mockHashMap.size());
}

1.2 @Spy

@Spy注解用于创建真实对象并监视该真实对象。 间谍程序可以帮助调用对象的所有常规方法,同时仍可以跟踪每次交互,就像使用模拟一样。

请注意,在给定的示例中,由于我们向其中添加了一个键值对,因此映射的大小如何保持为 1。 我们还可以使用它的键取回添加到映射的值。 在模拟实例中是不可能的。

@Spy
HashMap<String, Integer> hashMap;

@Test
public void saveTest()
{
	hashMap.put("A", 10);

	Mockito.verify(hashMap, times(1)).put("A", 10);
	Mockito.verify(hashMap, times(0)).get("A");

	assertEquals(1, hashMap.size());
	assertEquals(new Integer(10), (Integer) hashMap.get("A"));
}

@Mock@Spy之间的区别

使用@Mock时,mockito 将创建类的准系统的 shell 实例,完全可以跟踪与之的交互。 这不是真实的对象,也不维护其状态更改。

使用@Spy时,mockito 创建类的真实实例并跟踪与之的每次交互。 它保持状态更改。

1.3 @Captor

@Captor注解用于创建ArgumentCaptor实例,该实例用于捕获方法参数值进行进一步的声明。

请注意,mockito 使用参数类的equals()方法验证参数值。

@Mock
HashMap<String, Integer> hashMap;

@Captor
ArgumentCaptor<String> keyCaptor;

@Captor
ArgumentCaptor<Integer> valueCaptor;

@Test
public void saveTest() 
{
	hashMap.put("A", 10);

	Mockito.verify(hashMap).put(keyCaptor.capture(), valueCaptor.capture());

	assertEquals("A", keyCaptor.getValue());
	assertEquals(new Integer(10), valueCaptor.getValue());
}

1.4 @InjectMock

在模拟中,我们需要创建要测试的类的对象,然后插入其依赖项(模拟)以完全测试行为。 为此,我们使用@InjectMock注解。

@InjectMock标记应在其上执行注射的字段。 Mockito 将尝试仅通过构造器注入,设置器注入或属性注入按此顺序注入模拟。 如果任何给定的注射策略失败,则 Mockito 不会报告失败。

阅读更多: @Mock@InitMock注解之间的区别

2. 如何初始化 Mockito 注解

为了使用上述注解,测试类应使用以下三种给定方法之一初始化注解:

  1. 在单元测试课的顶部使用@RunWith(MockitoJUnitRunner.class)

    @RunWith(MockitoJUnitRunner.class)
    public class ExampleTest {
    
        @Mock
        private List list;
    
        @Test
        public void shouldDoSomething() {
            list.add(100);
        }
    }
    
    
  2. 在单元测试类的@Before方法中使用MockitoAnnotations.initMock(this)

    public class ExampleTest {
    
        @Mock
        private List list;
    
        @Before
        public void setup() {
            MockitoAnnotations.initMock(this);
        }
    
        @Test
        public void shouldDoSomething() {
            list.add(100);
        }
    }
    
    
  3. 使用MockitoJUnit.rule()创建MockitoRule类。

    public class ExampleTest {
    
        @Rule
        public MockitoRule rule = MockitoJUnit.rule();
    
        @Mock
        private List list;
    
        @Test
        public void shouldDoSomething() {
            list.add(100);
        }
    }
    
    

在评论中,将您与 Mockito 注解有关的问题放到我这里。

学习愉快!

参考文献:

Mock Java Doc
Spy Java Doc
Captor Java Doc
InjectMock Java Doc

Mockito – @Mock@InjectMock注解之间的区别

原文: https://howtodoinjava.com/mockito/mockito-mock-injectMock/

在使用 junit 和 Mockito 进行单元测试的过程中,我们使用@Mock@InjectMock注解来创建要测试的对象和依赖项。 了解 mockito 中@Mock@InjectMock注解之间的区别

1. 模拟对象与存根对象之间的区别

重要的是要了解模拟对象与存根对象之间的区别。 对象是类的实际实例,使用对象引用调用的任何方法都将执行类文件中定义的方法主体。

模拟对象是用于隐藏无法在测试环境中测试的依赖项的接口。 数据库,网络位置等。使用模拟引用调用的方法不会执行类文件中定义的方法主体,而是使用when-thenReturn方法组合来配置方法行为。

在 junit 测试中,我们为需要测试的类和要调用的方法创建对象。 我们为依赖项创建了模拟,这些依赖项不会出现在测试环境中,并且对象依赖于该依赖项来完成方法调用。

2. @Mock@InjectMock之间的区别

在基于 Mockito 的 junit 测试中,@Mock注解创建模拟,而@InjectMock创建类对象

@Mock – 创建模拟
@InjectMock – 创建对象并注入模拟的依赖项

  • 使用@InjectMock创建需要在测试类中进行测试的类实例。
  • 当需要为给定类执行实际方法主体时,请使用@InjectMock
  • 当我们需要使用模拟对象初始化的所有内部依赖项以正常工作方法时,请使用@InjectMock
  • 使用@Mock创建模拟,以支持要测试的类的测试。
  • 带有@Mock注解的带注解的类(待测试)依赖项。
  • 我们必须为模拟对象定义when-thenRetrun方法,在实际测试执行期间将调用哪些类方法。

3. @Mock@InjectMock示例

让我们通过示例来了解@Mock@InjectMock之间的区别。 在此示例中,我们具有类MainClass,其类具有方法save()

MainClass依赖于DatabaseDAONetworkDAO。 当我们调用MainClass.save()方法时,它在内部调用两个相关对象的保存方法。

3.1 Java 类

public class MainClass {

	DatabaseDAO database;
	NetworkDAO network;

	//Setters and getters

	public boolean save(String fileName) 
	{
		database.save(fileName);
		System.out.println("Saved in database in Main class");

		network.save(fileName);
		System.out.println("Saved in network in Main class");

		return true;
	}
}

public class DatabaseDAO {
	public void save(String fileName) {
		System.out.println("Saved in database");
	}
}

public class NetworkDAO {
	public void save(String fileName) {
		System.out.println("Saved in network location");
	}
}

3.2 MainClass的 Junit 测试

让我们为MainClass编写 junit 测试。

import static org.junit.Assert.assertEquals;

import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMock;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

public class ApplicationTest 
{
	@InjectMock
	MainClass mainClass;

	@Mock
	DatabaseDAO dependentClassOne;

	@Mock
	NetworkDAO dependentClassTwo;

	@Before
	public void init() {
		MockitoAnnotations.initMock(this);
	}

	@Test
	public void validateTest()
	{
		boolean saved = mainClass.save("temp.txt");
		assertEquals(true, saved);
	}
}

测试成功执行,我们在控制台中获得以下语句。 仔细观察控制台输出。

Saved in database in Main class
Saved in network in Main class

在上面的测试中,将打印用MainClass.save()方法编写的语句,但不打印从属类。 它证明了方法主体仅针对MainClasssave()方法执行,因为我们使用@InjectMock注解创建了MainClass的实际对象。 对于依赖类,我们使用了模拟。

3.3 验证模拟的方法调用

要验证是否已调用模拟方法,我们必须使用模拟提供的工具方法verify()。 它有助于验证在执行实际的测试类方法时是否已执行了模拟方法。

@Test
public void validateTest()
{
	boolean saved = mainClass.save("temp.txt");
	assertEquals(true, saved);

	verify(dependentClassOne, times(1)).save("temp.txt");
	verify(dependentClassTwo, times(1)).save("temp.txt");
}

4 总结

在此 mockito 教程中,我们了解了@Mock@InjectMock注解之间的区别。 我们了解了在 junit 测试中将这些注解应用于类时发生了什么。

学习愉快!

参考: Mockito 指南

Eclipse 项目中的 Maven 自定义原型

原文: https://howtodoinjava.com/maven/maven-custom-archetype/

Maven 自定义原型帮助创建特定类型的项目结构,而任何标准的 Maven 原型均无法使用。 在这个 Maven 教程中,我们将学习从一个 Eclipse 项目创建创建自定义原型

特别是在某些情况下,当我们在生成的 maven 项目中需要自定义内容时,这是非常有用的,而 maven 并没有提供现成的内容。

Table of Contents

Why custom archetype is required?
Create Custom Archetype
Import Project to Eclipse
Create Template Files
Create new projects with custom archetype

为什么需要自定义原型?

通常,Maven 和一些第三方提供商会提供一些原型,这些原型对于轻松启动我们的 Maven 项目很有用。 但是在我们的日常工作中,由于以下原因,我们可能会遇到一些需要创建自定义项目结构的场景:

  • 需要在组织内引入/强制执行特定的项目结构(包括程序包和框架类)。
  • 通过快速开始实际工作,减少了建立项目结构的时间,提高了开发人员的生产率。
  • 通过确保项目结构和预期的工件到位,减少了代码检查的工作量。

创建自定义原型

Maven 已经为创建新原型的提供了一种新原型maven-archetype-archetype

mvn archetype:generate -B -DarchetypeArtifactId=maven-archetype-archetype  -DgroupId=com.howtodoinjava.archetype -DartifactId=maven-howtodoinjava-archetype -Dversion=1.0-SNAPSHOT

让我们了解以上命令。

  • -DarchetypeArtifactId=maven-archetype-archetype是 maven 提供的用于创建新的自定义原型的原型。
  • -DgroupId=com.howtodoinjava.archetype是我们现在将创建的原型的组 ID。
  • -DartifactId=maven-howtodoinjava-archetype是我们现在将创建的原型的artifactId
  • -Dversion=1.0-SNAPSHOT是 Maven 原型的版本。

这里的所有参数都是不言自明和直观的。 尽管如此,我们仍然可以随时遵循官方 Maven 文档以获取更多详细信息。

现在,我们需要在命令提示符下运行此命令,然后确保已设置 maven 类路径

因此,一旦在命令提示符下运行此命令,您将在启动mvn命令的同一目录中生成一个 maven 项目。

导入项目到 Eclipse

现在的下一个工作是将这个项目导入 Eclipse 中,以进一步完善以满足我们的要求。 导入后,eclipse 项目结构将如下所示。

Folder Structure of Imported Project

导入项目的文件夹结构

将其导入 eclipse 后,我们需要执行以下步骤。

  • 删除/src/main/resources/archetype-resources/src/main/java/src/main/resources/archetype-resources/src/test/java的内容–主要是App.javaAppTest.java
  • 我们需要将文件/src/main/resources/META-INF/maven/archetype.xml重命名为archetype-metadata.xml,稍后我们需要更改该文件的内容,我们将在一段时间后看到。

完成上述步骤后,文件夹结构将如下所示:

Project Structure after Changes

变更后的项目结构

创建模板文件

现在,我们将为原型应每次生成的类和资源创建一些模板文件。

  • \src\main\resources\archetype-resources\src\main\java\__project-name__.java中创建模板 Java 文件,其内容为:

    package ${package};
    
    public class ${project-name} {
    
        public static void ${project-name}SampleMethod() {
          System.out.println("Sample method generated by maven Archetype..");
        }
    
    }
    
    

    该模板文件将根据运行时提供的占位符的值生成 Java 文件。 文件名中的占位符必须用__NAME__包围,并且占位符逻辑名称分隔符应为连字符(-)。

  • 为属性文件创建模板; 具有属性键值模板,例如:
    ${project-name}.key=This is ${project-name} test property,文件名将为\src\main\resources\archetype-resources\src\main\resources\__property-file-name__.properties
    ,因为我们想在运行时生成文件名,因此我们在文件名中放置了一个占位符。

  • 随意根据您的需求创建任意数量的模板文件。

  • 现在我们需要像下面那样修改archetype-metadata.xml

    requiredProperties部分将声明从该原型生成项目时所需的所有属性及其默认值。

    fileSets将声明将在最终生成的项目中放置的文件。

    <?xml version="1.0" encoding="UTF-8"?>
    <archetype-descriptor name="basic">
    
    	<requiredProperties>
    		<requiredProperty key="project-name" />
    		<requiredProperty key="property-file-name">
    			<defaultValue>Resource-default</defaultValue>
    		</requiredProperty>
    
    		<!--JUnit version to use in generated project -->
    		<requiredProperty key="junit-version">
    			<defaultValue>4.10</defaultValue>
    		</requiredProperty>
    	</requiredProperties>
    
    	<!--Add new fileset for resources -->
    	<!--Set filtered="true" to process files in that directory as templates -->
    	<!--Set packaged="false" to remove package folder structure from resource 
    		directory -->
    	<fileSets>
    		<fileSet filtered="true">
    			<directory>src/main/resources</directory>
    
    			<!--Filters example -->
    			<includes>
    				<include>*.txt</include>
    				<include>*.properties</include>
    			</includes>
    			<excludes>
    				<exclude>**/*.xml</exclude>
    			</excludes>
    		</fileSet>
    
    		<fileSet filtered="true" packaged="true">
    			<directory>src/main/java</directory>
    		</fileSet>
    		<fileSet filtered="true" packaged="true">
    			<directory>src/test/java</directory>
    		</fileSet>
    	</fileSets>
    
    </archetype-descriptor>
    
    
  • 我们还需要更改archetype-resources文件夹下的pom.xml文件,以接受运行时 GAV(GroupId:ArtifactId:Version)坐标。 为了接受运行时值,我们需要将其更改为占位符,对于将使用此原型生成的项目。

    archetype-resources下的pom.xml应该看起来像

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<groupId>${groupId}</groupId>
    	<artifactId>${artifactId}</artifactId>
    	<version>${version}</version>
    
    	<dependencies>
    		<dependency>
    			<groupId>junit</groupId>
    			<artifactId>junit</artifactId>
    			<version>${junit-version}</version>
    			<scope>test</scope>
    		</dependency>
    	</dependencies>
    </project>
    
    

    因此,我们基本上完成了与原型更改相关的更改,现在应该通过命令mvn clean install来构建原型 maven 项目。 该项目应该构建良好,我们已经准备好使用此原型来创建新的 Maven 项目。

使用自定义原型创建新项目

成功构建原型项目并将此项目成功安装到本地后,我们将运行以下命令来创建 maven 项目。

mvn archetype:generate -DarchetypeCatalog=local -DarchetypeArtifactId=maven-howtodoinjava-archetype -DarchetypeGroupId=com.howtodoinjava.archetype -DarchetypeVersion=1.0-SNAPSHOT

Maven 将启动交互模式,并询问有关新 Maven 项目的所有必需属性。 具有默认值的属性将被跳过,但是如果您在上一步中没有确认配置,则可以覆盖默认值。

自定义原型输入例如:

Create Custom archetypes Example

创建自定义原型示例

完成此步骤后,我们应该根据已开发的模板创建一个新项目。

因此,展望未来,如果您处于任何情况下,默认的 Maven 原型都不足够,则可以使用 Maven 的隐藏功能来创建自己的自定义原型。

下载源码

学习愉快!

Mockito – 验证具有不同参数的多个方法调用

原文: https://howtodoinjava.com/mockito/verify-multiple-method-arguments/

学习编写单元测试,该单元测试使用不同的参数多次调用一个方法,然后分别验证方法调用和方法参数。

1. 验证具有不同参数的多个方法调用

给定的单元测试模拟了HashMap类,并在put(key, value)代码中调用了两次。 然后,它验证该方法已被调用两次。 该测试进一步分别验证所有不同的方法参数。

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.times;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class MockitoExample 
{
	@Mock
	HashMap<String, Integer> hashMap;

	@Captor
	ArgumentCaptor<String> keyCaptor;

	@Captor
	ArgumentCaptor<Integer> valueCaptor;

	@Test
	public void saveTest() 
	{
		hashMap.put("A", 10);
		hashMap.put("B", 20);

		//1\. Verify method was invoked N times

		Mockito.verify(hashMap, times(2)).put(keyCaptor.capture(), valueCaptor.capture());

		List<String> keys = keyCaptor.getAllValues();
		List<Integer> values = valueCaptor.getAllValues();

		//2\. Verify method argument values as list

		assertEquals(Arrays.asList("A", "B"), keys);
		assertEquals(Arrays.asList(Integer.valueOf(10), Integer.valueOf(20)), values);

		//3\. Verify method arguments separately

		assertEquals("A", keys.get(0));
		assertEquals("B", keys.get(1));

		assertEquals(Integer.valueOf(10), values.get(0));
		assertEquals(Integer.valueOf(20), values.get(1));
	}
}

2. 解释

为了捕获并验证多次调用某个方法时传递给该方法的所有方法参数,我们将遵循以下步骤:

  • 使用Mockito.verify(mock, times(n))验证方法是否已执行'n'次。
  • 创建与方法中的参数数量一样多的ArgumentCaptor实例。 在上面的示例中,我们测试了适用于键值对的HashMap,因此我们创建了两个ArgumentCaptor实例–一个用于键,另一个用于值。
  • 使用ArgumentCaptor.getAllValues()检索所有执行期间传递给一个方法参数的所有值。 它返回传递的参数值的List
  • 我们可以使用assertEquals(expected, result)来验证预期的多个参数是否与ArgumentCaptor的检索值匹配。

将有关多次调用方法的问题交给我,并在基于模拟的单元测试中验证其方法不同的参数值。

学习愉快!

Spring Boot,Mockito 和 Junit – 单元测试服务层

原文: https://howtodoinjava.com/spring-boot2/testing/spring-boot-mockito-junit-example/

学习使用 JUnitMockito 测试框架为 Spring 应用的服务层编写单元测试。 本教程演示 SpringBoot 测试服务层示例

1. Maven 依赖

spring-boot-starter-test依赖项包括创建和执行测试所需的所有依赖项。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
</dependency>

如果不使用 spring boot,则包括以下依赖项。

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.15.0</version>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>2.15.0</version>
</dependency>

2. 起步

在此示例中,我们主要对两个类EmployeeManagerEmployeeDao进行单元测试。 顾名思义,管理器类代表服务层,而 dao 类正在与数据库进行交互。

EmployeeManager类依赖于EmployeeDao和委托方法调用来获取 pis 最终返回到控制器类的数据。

为了测试EmployeeManager中的方法,我们可以通过以下两种方式创建 JUnit 测试类 TestEmployeeManager

2.1 MockitoJUnitRunner

MockitoJUnitRunner类自动初始化所​​有带有@Mock@InjectMock注解的对象。

@RunWith(MockitoJUnitRunner.class)
public class TestEmployeeManager {

	@InjectMock
	EmployeeManager manager;

	@Mock
	EmployeeDao dao;

	//tests	
}

2.2 MockitoAnnotations.initMock()方法

如果我们不使用MockitoJUnitRunner类方法,则可以使用静态方法MockitoAnnotations.initMock()。 在初始化 junit 测试时,该方法还会初始化模拟对象。

public class TestEmployeeManager {

	@InjectMock
	EmployeeManager manager;

	@Mock
	EmployeeDao dao;

	@Before
	public void init() {
		MockitoAnnotations.initMock(this);
	}

	//tests	
}

2.3 @Mock vs @InjectMock

  • @Mock 注解会为其注解的类创建一个模拟实现。
  • @InjectMock 还将创建模拟实现,另外将标有注解@Mock的从属模拟注入其中。

在上面的示例中,我们用@InjectMock注解了EmployeeManager类,因此模仿者将为EmployeeManager类创建模仿对象,并将EmployeeDao的模仿依赖项注入其中。

3. 使用 Mockito 进行 JUnit 测试

让我们看几个例子,这些例子编写了 junit 测试,以使用通过模拟创建的模拟对象对服务层方法进行单元测试。

很少有示例方法是getAllEmployees(),该方法将返回EmployeeVO对象的列表,getEmployeeById(int id)以给定 id 返回雇员。 和createEmployee()将添加一个雇员对象并返回void

3.1 服务层测试

package com.howtodoinjava.demo;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMock;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;

import com.howtodoinjava.demo.dao.EmployeeDao;
import com.howtodoinjava.demo.model.EmployeeVO;
import com.howtodoinjava.demo.service.EmployeeManager;

public class TestEmployeeManager {

	@InjectMock
	EmployeeManager manager;

	@Mock
	EmployeeDao dao;

	@Before
	public void init() {
		MockitoAnnotations.initMock(this);
	}

	@Test
	public void getAllEmployeesTest()
	{
		List<EmployeeVO> list = new ArrayList<EmployeeVO>();
		EmployeeVO empOne = new EmployeeVO(1, "John", "John", "[email protected]");
		EmployeeVO empTwo = new EmployeeVO(2, "Alex", "kolenchiski", "[email protected]");
		EmployeeVO empThree = new EmployeeVO(3, "Steve", "Waugh", "[email protected]");

		list.add(empOne);
		list.add(empTwo);
		list.add(empThree);

		when(dao.getEmployeeList()).thenReturn(list);

		//test
		List<EmployeeVO> empList = manager.getEmployeeList();

		assertEquals(3, empList.size());
		verify(dao, times(1)).getEmployeeList();
	}

	@Test
	public void getEmployeeByIdTest()
	{
		when(dao.getEmployeeById(1)).thenReturn(new EmployeeVO(1,"Lokesh","Gupta","[email protected]"));

		EmployeeVO emp = manager.getEmployeeById(1);

		assertEquals("Lokesh", emp.getFirstName());
		assertEquals("Gupta", emp.getLastName());
		assertEquals("[email protected]", emp.getEmail());
	}

	@Test
	public void createEmployeeTest()
	{
		EmployeeVO emp = new EmployeeVO(1,"Lokesh","Gupta","[email protected]");

		manager.addEmployee(emp);

		verify(dao, times(1)).addEmployee(emp);
	}
}

单元测试结果将是这样。

Spring boot mockito junit example

Spring boot mockito junit 示例

3.2 服务层类

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.howtodoinjava.demo.dao.EmployeeDao;
import com.howtodoinjava.demo.model.EmployeeVO;

@Service
public class EmployeeManager 
{
	@Autowired
	EmployeeDao dao;

	public List<EmployeeVO> getEmployeeList() {
		return dao.getEmployeeList();
	}

	public EmployeeVO getEmployeeById(int id) {
		return dao.getEmployeeById(id);
	}

	public void addEmployee(EmployeeVO employee) {
		dao.addEmployee(employee);
	}
}

3.3 DAO 层类

package com.howtodoinjava.demo.dao;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Repository;

import com.howtodoinjava.demo.model.EmployeeVO;

@Repository
public class EmployeeDao {

	private Map<Integer, EmployeeVO> DB = new HashMap<>();

	public List<EmployeeVO> getEmployeeList() 
	{
		List<EmployeeVO> list = new ArrayList<>();
		if(list.isEmpty()) {
			list.addAll(DB.values());
		}
		return list;
	}

	public EmployeeVO getEmployeeById(int id) {
		return DB.get(id);
	}

	public void addEmployee(EmployeeVO employee) {
		employee.setEmployeeId(DB.keySet().size() + 1);
		DB.put(employee.getEmployeeId(), employee);
	}

	public void updateEmployee(EmployeeVO employee) {
		DB.put(employee.getEmployeeId(), employee);
	}

	public void deleteEmployee(int id) {
		DB.remove(id);
	}
}

4. Spring Boot Mockito 示例 – 总结

在此 mockito 教程中,我们学习了使用 JUnit 和 Mockito 对 Spring Boot 应用中的服务层进行单元测试。 我们学习了如何设置测试类并编写 JUnit 测试。

我们还了解了@Mock@InjectMock注解之间的区别

学习愉快!

标签:教程,其它,测试,class,HowToDoInJava,import,org,public,注解
From: https://www.cnblogs.com/apachecn/p/18500108

相关文章

  • StudyTonight-Web-中文教程-一-
    StudyTonightWeb中文教程(一)原文:StudyTonight协议:CCBY-NC-SA4.0HTMLHTML标签AHTML<a>标签原文:https://www.studytonight.com/html5-references/html-a-tagHTML<a>标签是一个锚点,用来创建一个超链接。超链接用于将当前网页与其他网页或互联网上可用的任何其他网......
  • ZetCode-Kotlin-教程-一-
    ZetCodeKotlin教程(一)原文:ZetCode协议:CCBY-NC-SA4.0KotlinHelloWorld教程原文:http://zetcode.com/kotlin/helloworld/KotlinHelloWorld教程展示了如何在Kotlin中创建HelloWorld程序。Kotlin是在Java虚拟机上运行的静态类型的编程语言。Kotlin由Jet......
  • ZetCode-Java-教程-二-
    ZetCodeJava教程(二)原文:ZetCode协议:CCBY-NC-SA4.0Java语法结构原文:http://zetcode.com/lang/java/lexis/像人类语言一样,计算机语言也具有词汇结构。Java程序的源代码由令牌组成。令牌是原子代码元素。在Java中,我们具有注释,标识符,字面值,运算符,分隔符和关键字。Ja......
  • ZetCode-GUI-教程-九-
    ZetCodeGUI教程(九)原文:ZetCode协议:CCBY-NC-SA4.0wxWidgets中的布局管理原文:http://zetcode.com/gui/wxwidgets/layoutmanagement/典型的应用由各种小部件组成。这些小部件放置在容器小部件内。程序员必须管理应用的布局。这不是一件容易的事。在wxWidgets中,我......
  • ZetCode-PHP-教程-一-
    ZetCodePHP教程(一)原文:ZetCode协议:CCBY-NC-SA4.0PHP教程原文:https://zetcode.com/lang/php/这是PHP教程。本教程涵盖了PHP编程语言的核心。它使用PHPCLI。PHP教程适合初学者。目录PHP语言词法结构基础知识数据类型字符串运算符控制流数组数组......
  • 计算机毕业设计项目推荐:大学生实习成绩评价系统的设计与实现38147(开题答辩+程序定制+
    摘 要21世纪的今天,随着社会的不断发展与进步,人们对于信息科学化的认识,已由低层次向高层次发展,由原来的感性认识向理性认识提高,管理工作的重要性已逐渐被人们所认识,科学化的管理,使信息存储达到准确、快速、完善,并能提高工作管理效率,促进其发展。论文主要是对大学生实习成绩......
  • 计算机毕业设计项目推荐,个人知识管理系统 79004(开题答辩+程序定制+全套文案 )上万套实
    摘 要尽管我们每天面临的信息越来越多,信息过载与信息噪音越来越严重,但只要我们能充分利用个人知识管理技能,借助有效的个人知识管理软件相信战胜海量信息不再是困难。本课题在分析了个人知识管理现状以及对现有的个人知识管理网站进行研究比较的基础上,针对网络交流互助的特......
  • vue3入门教程,一站学会全套vue!
    vue3vue3作为前端重要的框架,学会vue可以让你更加了解前端。本博客致力于让你一站学会vue3的全部内容,从小白到高手。全是干货,准备好了吗?文章目录vue3创建工程文档结构核心语法模板语法插值语法指令语法无参指令有参指令自定义指令setupsetup函数setup语法糖响应式数......
  • SQL:Windows下MySQL的安装教程(超详细)
    一.系统环境:操作系统:Windows11;MySQL版本:mysql-community-8.0.40.0;二.MySQL下载:访问MySQL官网下载地址:https://www.mysql.com/,点击DOWNLOADS;跳转后页面下滑找到框选链接并点击;跳转后点击框选链接;跳转后点击Download;5.当前下载页面需要登录但是也可以点......
  • 上交2024最新-《动手学大模型》实战教程及ppt分享!
    这份完整版的《动手学大模型》实战教程及ppt已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】本课介绍今天分享一个上海交大的免费的大模型课程,有相关教程文档和Slides,目前是2.2K星标,还是挺火的!文末附本课ppt及实战教程免费下载......