一、实验内容
- 实验目的
理解EJB,利用wildfly服务器容器进行远程调用。(笔者实现的是服务器端和客户端都在同一台电脑上) - 实验准备操作
a.下载安装java jdk 1.6或以上(笔者用的版本是1.8.0_201);
b.下载安装Maven(笔者用的版本是3.6.0)
c.熟悉IDEA或eclipse等开发环境(笔者用的版本是IDEA 2018.3.5)。
d.下载并安装Wildfly服务器(网上的教程大多是10.0.0版本,最新的版本是16.0.0,笔者用的版本是16.0.0)。
e.下载并安装Mysql数据库
f.windows10家庭版 - 实验内容
a.建立有状态的Java Bean,实现以下功能:
b.操作用户(录入人员)登陆后,显示本次登陆的次数和上一次登陆的时间;
c.操作用户登录后,可进行校友的检索、修改、删除、统计等功能;
d.5分钟如果没有操作,则自动登出系统;
e.操作用户退出时,显示用户连接的时间长度,并把此次登陆记录到数据库。
二、实验过程
1.代码打包如下:链接: https://pan.baidu.com/s/1mxV0BWVY3QjMjz3L5anSZQ 提取码: es83
2.主体代码:
- server MyRemote.java
package org.jboss.as.quickstarts.ejb.remote.stateful;
import java.util.List;
public interface MyRemote {
boolean login(String username, String password);
boolean mysql_init();
int update(String sql);
List query(String sql);
}
- server Bean.java
package org.jboss.as.quickstarts.ejb.remote.stateful;
import javax.ejb.Remote;
import javax.ejb.Stateful;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Stateful
@Remote(MyRemote.class)
public class Bean implements MyRemote {
//不知道为什么,我用resultset返回query的结果到client时,就会报错,缺少什么resultset的jdk,于是我把resultset转换成list返回
private List convertList(ResultSet rs) throws SQLException{
List list = new ArrayList();
ResultSetMetaData md = rs.getMetaData();//获取键名
int columnCount = md.getColumnCount();//获取行的数量
while (rs.next()) {
Map rowData = new HashMap();//声明Map
for (int i = 1; i <= columnCount; i++) {
rowData.put(md.getColumnName(i), rs.getObject(i));//获取键名及值
}
list.add(rowData);
}
return list;
}
//登录
public boolean login(String username, String password){
if(username.equals("cyd") && password.equals("cyd"))
return true;
else
return false;
}
//数据库初始化
public boolean mysql_init(){
boolean flag = false;
try {
Context context = new InitialContext();
DataSource dataSource = (DataSource) context.lookup("java:jboss/datasources/ExampleDS");
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
statement.executeUpdate("create table admin(login_time TIMESTAMP (23) not null);");//一定不能大于23,不然会报错,建表失败
//姓名、性别、生日、入学年份、毕业年份、工作城市/地区、工作单位、职务、手机、邮箱、微信
statement.executeUpdate("create table alumni(name char (20) not null, gender char (10), birthday char (20), " +
"entrance int (4), graduation int (4), location char (20), company char (20), job char (20)," +
"phone char(11) not null, mail char (20), wechat char (20));");
statement.executeUpdate("insert into alumni(name,gender,birthday,entrance,graduation,location,company,job,phone,mail,wechat)" +
"values('cyd','male','19971012','2016','2020','xiamen','xmu','student','15659833304','767937571@qq.com','cyd767937571')");
statement.executeUpdate("insert into alumni(name,gender,birthday,entrance,graduation,location,company,job,phone,mail,wechat)" +
"values('cxx','female','19941209','2014','2018','xiamen','liantong','manager','15659833303','7685946828@qq.com','wechat')");
flag = true;
connection.close();
statement.close();
} catch (NamingException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return flag;
}
//insert, delete, update用这个,返回的是更新的行数
public int update(String sql){
int ins = 0;
try {
Context context = new InitialContext();
DataSource dataSource = (DataSource) context.lookup("java:jboss/datasources/ExampleDS");
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ins = statement.executeUpdate(sql);
connection.close();
statement.close();
} catch (NamingException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return ins;
}
//select用这个,返回的是本来是结果集,现在被我转换成List
public List query(String sql){
List list = new ArrayList();
try {
Context context = new InitialContext();
DataSource dataSource = (DataSource) context.lookup("java:jboss/datasources/ExampleDS");
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery(sql);
list = convertList(rs);
connection.close();
statement.close();
} catch (NamingException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
}
- client RemoteEJBClient.java
package org.jboss.as.quickstarts.ejb.remote.client;
import org.jboss.as.quickstarts.ejb.remote.stateful.MyRemote;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.*;
public class RemoteEJBClient {
public static void main(String[] args) throws Exception {
// Invoke a stateful bean
invokeStatefulBean();
}
/**
* Looks up a stateful bean and invokes on it
*
* @throws NamingException
*/
private static void invokeStatefulBean() throws NamingException{
final MyRemote statefulRemoteCounter = lookupRemoteStatefulCounter();
statefulRemoteCounter.mysql_init();
boolean flag = true;
while(flag){
System.out.println("1:login");
System.out.println("others:exit");
Scanner input_login = new Scanner(System.in);
switch (input_login.nextInt()) {
case 1:
Scanner input_info = new Scanner(System.in);
System.out.println("username:");
String username = input_info.nextLine();
System.out.println("password:");
String password = input_info.nextLine();
if (statefulRemoteCounter.login(username, password)) {
Timestamp login_time = new Timestamp(System.currentTimeMillis());
List list_time = statefulRemoteCounter.query("select login_time from admin;");
if (list_time.isEmpty()) {
System.out.println("You have never logged in!");
} else {
int login_count = 1;
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
for (Object list : list_time) {
login_count++;
HashMap hm = (HashMap) list;
for (Object key : hm.keySet()) {
timestamp = (Timestamp) hm.get(key);
}
}
System.out.println("You have logged in to the system " + login_count + " times.");
System.out.println("The last time you logged into the system was " + timestamp + ".");
}
int result_int = statefulRemoteCounter.update(
"insert into admin(login_time)values('" + login_time + "');");
// if (result_int > 0) {
// System.out.println("insert data successfully!");
// }
System.out.println("login successfully");
boolean flag2 = true;
Timestamp init_time = new Timestamp(System.currentTimeMillis());
while(flag2) {
System.out.println("1:select");
System.out.println("2:update");
System.out.println("3:delete");
System.out.println("4:total");
System.out.println("5:insert");
System.out.println("others:logout");
Scanner input_operation = new Scanner(System.in);
int input_num = input_operation.nextInt();
Timestamp current_time = new Timestamp(System.currentTimeMillis());
float difftime = (current_time.getTime()-init_time.getTime())/1000;
if(difftime > 300){
System.out.println("You have logged out of the system because it has not been operated for a long time!");
current_time = new Timestamp(System.currentTimeMillis());
difftime = (current_time.getTime()-login_time.getTime())/1000;
System.out.println("Your login time is "+difftime+" seconds.");
break;
}
init_time = current_time;
switch (input_num){
case 1:
System.out.println("input the content you want to select(name,gender,birthday,entrance,graduation,location,company,job,phone,mail,wechat,*):");
Scanner input_content1 = new Scanner(System.in);
String content1 = input_content1.nextLine();
System.out.println("input the someone you want to show(where name = 'caiyuedong' or nothing):");
Scanner input_value1 = new Scanner(System.in);
String value1 = input_value1.nextLine();
List select_result1=statefulRemoteCounter.query("select "+content1+" from alumni "+value1+";");
if(select_result1.isEmpty()){
System.out.println("Check no such person!");
}else {
for (Object sr : select_result1) {
System.out.println(sr);
}
}
break;
case 2:
System.out.println("input the name you select:");
Scanner input_name2 = new Scanner(System.in);
String name2 = input_name2.nextLine();
List select_result2=statefulRemoteCounter.query("select * from alumni where name ='"+name2+"';");
if(select_result2.isEmpty()){
System.out.println("Check no such person!");
}else {
for (Object sr : select_result2) {
System.out.println(sr);
}
System.out.println("input the content you want to update(name,gender,birthday,entrance,graduation,location,company,job,phone,mail,wechat):");
Scanner input_content2 = new Scanner(System.in);
String content2 = input_content2.nextLine();
System.out.println("input the value you want to update:");
Scanner input_value2 = new Scanner(System.in);
String value2 = input_value2.nextLine();
int update_result = statefulRemoteCounter.update("update alumni set "+content2+"='"+value2+"' WHERE name='"+name2+"';");
if(update_result>0){
System.out.println("Update successfully");
}else {
System.out.println("Update unsuccessfully");
}
}
break;
case 3:
System.out.println("input the content you want to delete(name,gender,birthday,entrance,graduation,location,company,job,phone,mail,wechat):");
Scanner input_content3 = new Scanner(System.in);
String content3 = input_content3.nextLine();
System.out.println("input the value you want to delete:");
Scanner input_value3 = new Scanner(System.in);
String value3 = input_value3.nextLine();
System.out.println("Are you sure?Y/N");
Scanner input_sure3 = new Scanner(System.in);
String sure3 = input_sure3.nextLine();
if(sure3.equals("Y")){
statefulRemoteCounter.update("delete from alumni where "+content3+" = '"+value3+"';");
System.out.println("delete successfully");
}
break;
case 4:
int count = 0;
System.out.println("input the content you want to count(name,gender,birthday,entrance,graduation,location,company,job,phone,mail,wechat):");
Scanner input_content4 = new Scanner(System.in);
String content4 = input_content4.nextLine();
System.out.println("input the value you want to count:");
Scanner input_value4 = new Scanner(System.in);
String value4 = input_value4.nextLine();
List select_result4=statefulRemoteCounter.query("select * from alumni where "+content4+"='"+value4+"';");
if(select_result4.isEmpty()){
System.out.println("no person!");
}else {
for (Object sr : select_result4) {
System.out.println(sr);
count++;
}
System.out.println("There are "+count+" people totally");
}
break;
case 5:
System.out.println("input the value you want to insert(name,gender,birthday,entrance,graduation,location,company,job,phone,mail,wechat):");
System.out.println("such as 'cyd','male','19971012','2016','2020','xiamen','xmu','student','15659833304','767937571@qq.com','cyd767937571'");
Scanner input_name5 = new Scanner(System.in);
String value5 = input_name5.nextLine();
System.out.println(value5);
int insert_result = statefulRemoteCounter.update("insert into alumni(name,gender,birthday,entrance,graduation,location,company,job,phone,mail,wechat)" +
"values("+value5+")");
if(insert_result>0)
System.out.println("insert successfully");
break;
default:
current_time = new Timestamp(System.currentTimeMillis());
difftime = (current_time.getTime()-login_time.getTime())/1000;
System.out.println("Your login time is "+difftime+" seconds.");
flag2 = false;
break;
}
}
} else {
System.out.println("login unsuccessfully");
}
break;
default:
flag = false;
break;
}
}
}
/**
* Looks up and returns the proxy to remote stateful counter bean
*
* @return
* @throws NamingException
*/
private static MyRemote lookupRemoteStatefulCounter() throws NamingException {
final Hashtable<String, String> jndiProperties = new Hashtable<>();
jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
final Context context = new InitialContext(jndiProperties);
// The JNDI lookup name for a stateful session bean has the syntax of:
// ejb:<appName>/<moduleName>/<distinctName>/<beanName>!<viewClassName>?stateful
//
// <appName> The application name is the name of the EAR that the EJB is deployed in
// (without the .ear). If the EJB JAR is not deployed in an EAR then this is
// blank. The app name can also be specified in the EAR's application.xml
//
// <moduleName> By the default the module name is the name of the EJB JAR file (without the
// .jar suffix). The module name might be overridden in the ejb-jar.xml
//
// <distinctName> : WildFly allows each deployment to have an (optional) distinct name.
// This example does not use this so leave it blank.
//
// <beanName> : The name of the session been to be invoked.
//
// <viewClassName>: The fully qualified classname of the remote interface. Must include
// the whole package name.
// let's do the lookup
return (MyRemote) context.lookup("ejb:/wildfly-ejb-remote-server-side/Bean!"
+ MyRemote.class.getName() + "?stateful");
}
}
3.我的实验过程
我的代码是在一个demo(链接: https://pan.baidu.com/s/1OAkiqpxF2e9W7uOynX1Y4A 提取码: qezh)的基础上修改的。实验思路很清晰,用server连接数据库,然后在client端传送操作指令到server与数据库交互。刚开始的时候,打算使用JDBC连接数据库,但是每次都会报以下错误
java.lang.ClassNotFoundException: jdbc:mysql://127.0.0.1:3306/ejb_remote
在网上查找了很多相关的教程,例如在server的文件右键中的“Open Module Setting”添加jar包依赖,修改pom.xml文件等,都没办法解决这个问题,(有关于以上两个方法见后面的分享),于是我改用JNDI连接Mysql,(有关于JDBC和JDNI的优劣等见后面的分享),核心代码如下:
Context context = new InitialContext();
DataSource dataSource = (DataSource) context.lookup("java:jboss/datasources/ExampleDS");//连接默认的数据库
Connection connection = dataSource.getConnection();//创建连接
Statement statement = connection.createStatement();//用来执行sql语句
statement.executeUpdate("create table admin(login_time TIMESTAMP (23) not null);");//.executeUpdate返回的是受影响的行数,executeQuery返回的ResultSet,即操作的结果集合
connect.close;
statement.close;
接下来是最坑的阶段了,我以为wildfly要自己手动配置,于是我从官网上https://wildfly.org/downloads/下载了wildfly16.0.0,但是DataSource的配置一直配置不好,于是我更改版本,换成wildfly10.0.0版本(链接: https://pan.baidu.com/s/12DtYy022icKQaHlnSFllcQ 提取码: ijxb ),重新配置数据源,(配置数数据源的方法见后面的分享),这次成功了(科学的终点是玄学?有时候我也搞不懂,按照教程来,有时候可以,有时候不行),到这个时候我才发现,原来我第一次运行demo的时候,这个demo会自动下载并部署好一个wildfly16.0.0,以后的每次运行都是重新加载wildfly16.0.0,可以理解成重置,同时默认连接好Mysql,即数据源java:jboss/datasources/ExampleDS,每次重启server的时候,Mysql都会清空,所以要重新建表,初始化表。运行server后,会生成一个target文件夹,里面有一些配置,包括wildfly的文件夹。
所以其实如果知道这个东西之后,实验的内容就很简单了,直接在MyRemote.java里面设置接口,在Bean.java里面实现函数,从Client里面调用函数,处理好逻辑和熟悉SQL语句的话根本不难。以下是一些运行结果的截图
三、分享及我遇见的各种bug(满满的都是干货)
1.wildfly安装与环境配置,以及一些操作细节(上述的代码无需安装和配置wildfly)
- 安装:直接解压缩包到目录即可,注意路径中尽量不要出现中文或者空格,(其实很多的地方都会这么说明,避免因为编码问题出现bug)
- 配置环境变量:我的电脑->右键属性->高级系统设置->高级->环境变量->系统变量,点击下面的“新建”按钮,输入变量名为:JBOSS_HOME,变量值为:D:\wildfly-16.0.0.Final,这个变量值更改为自己的安装路径,然后再找到path,双击或者点击下面的”编辑“,点击”新建“,输入%JBOSS_HOME%\bin,一路确定退出就行了。
- tip1:如果有安装多个wildfly,需要更换不同的wildfly的版本,直接在上述的JBOSS_HOME修改成其他版本相对应的路径即可。
-
tip2:打开wildfly的目录,进入bin,打开add-user.bat,添加管理员账户,一路按照提示操作就行,然后打开同目录下的standalone.bat,不要关闭,在浏览器中( chrome天下第一)输入127.0.0.1:8080,如果成功进入如下界面,则证明安装成功。
然后可以点击Administration Console,进入管理界面,如果出现下图情况,
重新打开add-user.bat,添加一下账户就行了,如果还是不行的话,重启电脑一下,然后打开standalone.bat,记住这个standalone.bat不能关闭,再进入这个网站就行了。如果顺利的话会出现这个
输入设置好的账户密码登录之后可以看见这个
以下是配置数据源的地方
点击“view”进入以下界面
点击”disable“之后就可以编辑相关信息了,最后在connection里面点击test connection,如果显示
就证明配置数据源成功了。
-
tip3:配置数据源(单独拎出来说明一下)
在配置数据源的地方,点击”add“,选择”MYSQL DATASOURCE“,设置name和jndi name等等,直到以下这步
要将connection url里面3306/后面的名称改成你在mysql里要使用的database的名字。
- tip4:https://dev.mysql.com/doc/index-connectors.htmlmysql和java连接的jar包,添加到项目的目录中,然后在项目的pom.xml文件内,找到dependencies,模拟
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
或者直接在下图添加
- tip5:jboss7之后就改名为wildfly了,所以如果搜wildfly找不到攻略的话,可以尝试搜一下jboss。
2.jdbc和jdni
JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序.
JNDI全名为Java Naming and Directory Interface.JNDI主要提供应用程序所需要资源上命名与目录服务。在Java EE环境中,JNDI扮演了一个很重要的角色,它提供了一个接口让用户在不知道资源所在位置的情形下,取得该资源服务。就好比网络磁盘驱动器的功能一样。如果有人事先将另一台机器上的磁盘驱动器接到用户的机器上,用户在使用的时候根本就分辨不出现在的驱动器是存在本端,还是在另一端的机器上,用户只需取得资源来用,根本就不知道资源在什么地方。
以上概念来自百度百科。
- jdbc的连接操作简单,核心代码如下
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/mysqldb";
String username = "root";
String password = "";
Connection conn = null;
try {
Class.forName(driver); //classLoader,加载对应驱动
conn = (Connection) DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
- jdni的核心代码
Context context = new InitialContext();
DataSource dataSource = (DataSource) context.lookup("java:jboss/datasources/ExampleDS");//连接默认的数据库
Connection connection = dataSource.getConnection();//创建连接
Statement statement = connection.createStatement();//用来执行sql语句
statement.executeUpdate("create table admin(login_time TIMESTAMP (23) not null);");//.executeUpdate返回的是受影响的行数,executeQuery返回的ResultSet,即操作的结果集合
connect.close;
statement.close;
- 对比
我觉得这个博客的内容说的比较详细,相比我个人的理解更加全面https://www.cnblogs.com/panjun-Donet/articles/1182204.html。
但是总而言之,jdbc需要先找到数据库的驱动,(Class.forNAME),修改数据库时需要重新修改代码,jdni只需要修改服务器配置。jndi的适用范围更广,鲁棒性更强。
3.运行的代码解读
server端的运行结果,挑选几条特别关心
[org.jboss.as] (MSC service thread 1-2) WFLYSRV0049: WildFly Full 16.0.0.Final (WildFly Core 8.0.0.Final) starting ——启动的是默认配置的wildfly16
[org.jboss.as.connector.subsystems.datasources] (MSC service thread 1-7) WFLYJCA0001: Bound data source [java:jboss/datasources/ExampleDS] ——绑定的数据源是默认配置的ExampleDS
[org.jboss.as.server] (management-handler-thread - 1) WFLYSRV0010: Deployed "wildfly-ejb-remote-server-side.jar" (runtime-name : "wildfly-ejb-remote-server-side.jar") ——到这步说明部署成功,可以开始运行client端,在这之前运行client会报错,大概意思是连接被拒绝。
4.总结
兜兜转转,配环境那么久到最后发现demo已经帮你搞定一切了,只需要直接编写逻辑代码就行,有点难受,不过实际配环境这么久,查了很多官方文档和看了很多博客,也学习到了很多东西,至少我对wildfly、maven、jdni的运用的了解加深了很多。
以上有小部分内容引用了其他博客的内容,都有附上链接,以及一些代码等也有放在百度网盘里了,如果有侵权的地方,我会立即删除。以上,终于完成这个作业了,舒服。
网友评论