百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 优雅编程 > 正文

Mybatis 分页详解(mybatis分页语句)

sinye56 2024-10-07 14:29 5 浏览 0 评论

前言

在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。

分页的几种方式

1. 内存分页

内存分页的原理比较sb,就是一次性查询数据库中所有满足条件的记录,将这些数据临时保存在集合中,再通过List的subList方法,获取到满足条件的记录,由于太sb,直接忽略该种方式的分页。

2. 物理分页

在了解到通过内存分页的缺陷后,我们发现不能每次都对数据库中的所有数据都检索。然后在程序中对获取到的大量数据进行二次操作,这样对空间和性能都是极大的损耗。所以我们希望能直接在数据库语言中只检索符合条件的记录,不需要在通过程序对其作处理。这时,物理分页技术横空出世。

物理分页是借助sql语句进行分页,比如mysql是通过limit关键字,oracle是通过rownum等;其中mysql的分页语法如下:

select * from table limit 0,30

MyBatis 分页

1.借助sql进行分页

通过sql语句进行分页的实现很简单,我们先在StudentMapper接口中添加sql语句的查询方法,如下:

List<Student> queryStudentsBySql(@Param("offset") int offset, @Param("limit") int limit);

StudentMapper.xml 配置如下:

<select id="queryStudentsBySql" parameterType="map" resultMap="studentmapper">
 select * from student limit #{offset} , #{limit}
</select>

客户端使用的时候如下:

public List<Student> queryStudentsBySql(int offset, int pageSize) {
 return studentMapper.queryStudentsBySql(offset,pageSize);
 }

sql分页语句如下:select * from table limit index, pageSize;

缺点:虽然这里实现了按需查找,每次检索得到的是指定的数据。但是每次在分页的时候都需要去编写limit语句,很冗余, 其次另外如果想知道总条数,还需要另外写sql去统计查询。而且不方便统一管理,维护性较差。所以我们希望能够有一种更方便的分页实现。

2. 拦截器分页

拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。打个比方,对于Executor,Mybatis中有几种实现:BatchExecutor、ReuseExecutor、SimpleExecutor和CachingExecutor。这个时候如果你觉得这几种实现对于Executor接口的query方法都不能满足你的要求,那怎么办呢?是要去改源码吗?当然不。我们可以建立一个Mybatis拦截器用于拦截Executor接口的query方法,在拦截之后实现自己的query方法逻辑,之后可以选择是否继续执行原来的query方法。

Interceptor接口

对于拦截器Mybatis为我们提供了一个Interceptor接口,通过实现该接口就可以定义我们自己的拦截器。我们先来看一下这个接口的定义:

package org.apache.ibatis.plugin; 
 
import java.util.Properties; 
 
public interface Interceptor { 
 
 Object intercept(Invocation invocation) throws Throwable; 
 
 Object plugin(Object target); 
 
 void setProperties(Properties properties); 
 
} 

我们可以看到在该接口中一共定义有三个方法,intercept、plugin和setProperties。plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法,当然也可以调用其他方法,这点将在后文讲解。setProperties方法是用于在Mybatis配置文件中指定一些属性的。

定义自己的Interceptor最重要的是要实现plugin方法和intercept方法,在plugin方法中我们可以决定是否要进行拦截进而决定要返回一个什么样的目标对象。而intercept方法就是要进行拦截的时候要执行的方法。

对于plugin方法而言,其实Mybatis已经为我们提供了一个实现。Mybatis中有一个叫做Plugin的类,里面有一个静态方法wrap(Object target,Interceptor interceptor),通过该方法可以决定要返回的对象是目标对象还是对应的代理。这里我们先来看一下Plugin的源码:

package org.apache.ibatis.plugin; 
 
import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.Method; 
import java.lang.reflect.Proxy; 
import java.util.HashMap; 
import java.util.HashSet; 
import java.util.Map; 
import java.util.Set; 
 
import org.apache.ibatis.reflection.ExceptionUtil; 
 
public class Plugin implements InvocationHandler { 
 
 private Object target; 
 private Interceptor interceptor; 
 private Map<Class<?>, Set<Method>> signatureMap; 
 
 private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { 
 this.target = target; 
 this.interceptor = interceptor; 
 this.signatureMap = signatureMap; 
 } 
 
 public static Object wrap(Object target, Interceptor interceptor) { 
 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); 
 Class<?> type = target.getClass(); 
 Class<?>[] interfaces = getAllInterfaces(type, signatureMap); 
 if (interfaces.length > 0) { 
 return Proxy.newProxyInstance( 
 type.getClassLoader(), 
 interfaces, 
 new Plugin(target, interceptor, signatureMap)); 
 } 
 return target; 
 } 
 
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
 try { 
 Set<Method> methods = signatureMap.get(method.getDeclaringClass()); 
 if (methods != null && methods.contains(method)) { 
 return interceptor.intercept(new Invocation(target, method, args)); 
 } 
 return method.invoke(target, args); 
 } catch (Exception e) { 
 throw ExceptionUtil.unwrapThrowable(e); 
 } 
 } 
 
 private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { 
 Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); 
 if (interceptsAnnotation == null) { // issue #251 
 throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); 
 } 
 Signature[] sigs = interceptsAnnotation.value(); 
 Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>(); 
 for (Signature sig : sigs) { 
 Set<Method> methods = signatureMap.get(sig.type()); 
 if (methods == null) { 
 methods = new HashSet<Method>(); 
 signatureMap.put(sig.type(), methods); 
 } 
 try { 
 Method method = sig.type().getMethod(sig.method(), sig.args()); 
 methods.add(method); 
 } catch (NoSuchMethodException e) { 
 throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); 
 } 
 } 
 return signatureMap; 
 } 
 
 private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) { 
 Set<Class<?>> interfaces = new HashSet<Class<?>>(); 
 while (type != null) { 
 for (Class<?> c : type.getInterfaces()) { 
 if (signatureMap.containsKey(c)) { 
 interfaces.add(c); 
 } 
 } 
 type = type.getSuperclass(); 
 } 
 return interfaces.toArray(new Class<?>[interfaces.size()]); 
 } 
 
} 

我们先看一下Plugin的wrap方法,它根据当前的Interceptor上面的注解定义哪些接口需要拦截,然后判断当前目标对象是否有实现对应需要拦截的接口,如果没有则返回目标对象本身,如果有则返回一个代理对象。而这个代理对象的InvocationHandler正是一个Plugin。所以当目标对象在执行接口方法时,如果是通过代理对象执行的,则会调用对应InvocationHandler的invoke方法,也就是Plugin的invoke方法。所以接着我们来看一下该invoke方法的内容。这里invoke方法的逻辑是:如果当前执行的方法是定义好的需要拦截的方法,则把目标对象、要执行的方法以及方法参数封装成一个Invocation对象,再把封装好的Invocation作为参数传递给当前拦截器的intercept方法。如果不需要拦截,则直接调用当前的方法。Invocation中定义了定义了一个proceed方法,其逻辑就是调用当前方法,所以如果在intercept中需要继续调用当前方法的话可以调用invocation的procced方法。

这就是Mybatis中实现Interceptor拦截的一个思想,如果用户觉得这个思想有问题或者不能完全满足你的要求的话可以通过实现自己的Plugin来决定什么时候需要代理什么时候需要拦截。以下讲解的内容都是基于Mybatis的默认实现即通过Plugin来管理Interceptor来讲解的。

对于实现自己的Interceptor而言有两个很重要的注解,一个是@Intercepts,其值是一个@Signature数组。@Intercepts用于表明当前的对象是一个Interceptor,而@Signature则表明要拦截的接口、方法以及对应的参数类型。

首先我们看一下拦截器的具体实现,在这里我们需要拦截所有以PageDto作为入参的所有查询语句,自动以拦截器需要继承Interceptor类,PageDto代码如下:

?
import java.util.Date;
import java.util.List;
?
/**
 * Created by chending on 16/3/27.
 */
public class PageDto<T> {
?
 private Integer rows = 10;
?
 private Integer offset = 0;
?
 private Integer pageNo = 1;
?
 private Integer totalRecord = 0;
?
 private Integer totalPage = 1;
?
 private Boolean hasPrevious = false;
?
 private Boolean hasNext = false;
?
 private Date start;
?
 private Date end;
?
 private T searchCondition;
?
 private List<T> dtos;
?
?
 public Date getStart() {
 return start;
 }
?
 public void setStart(Date start) {
 this.start = start;
 }
?
 public Date getEnd() {
 return end;
 }
?
 public void setEnd(Date end) {
 this.end = end;
 }
?
 public void setDtos(List<T> dtos){
 this.dtos = dtos;
 }
?
 public List<T> getDtos(){
 return dtos;
 }
?
 public Integer getRows() {
 return rows;
 }
?
 public void setRows(Integer rows) {
 this.rows = rows;
 }
?
 public Integer getOffset() {
 return offset;
 }
?
 public void setOffset(Integer offset) {
 this.offset = offset;
 }
?
 public Integer getPageNo() {
 return pageNo;
 }
?
 public void setPageNo(Integer pageNo) {
 this.pageNo = pageNo;
 }
?
 public Integer getTotalRecord() {
 return totalRecord;
 }
?
 public void setTotalRecord(Integer totalRecord) {
 this.totalRecord = totalRecord;
 }
?
?
 public T getSearchCondition() {
 return searchCondition;
 }
?
 public void setSearchCondition(T searchCondition) {
 this.searchCondition = searchCondition;
 }
?
 public Integer getTotalPage() {
 return totalPage;
 }
?
 public void setTotalPage(Integer totalPage) {
 this.totalPage = totalPage;
 }
?
 public Boolean getHasPrevious() {
 return hasPrevious;
 }
?
 public void setHasPrevious(Boolean hasPrevious) {
 this.hasPrevious = hasPrevious;
 }
?
 public Boolean getHasNext() {
 return hasNext;
 }
?
 public void setHasNext(Boolean hasNext) {
 this.hasNext = hasNext;
 }
}
?

自定义拦截器PageInterceptor 代码如下:

?
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
import me.ele.elog.Log;
import me.ele.elog.LogFactory;
import me.ele.gaos.common.util.CommonUtil;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
?
?
/**
 *
 * 分页拦截器,用于拦截需要进行分页查询的操作,然后对其进行分页处理。
 *
 */
@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class,Integer.class})})
public class PageInterceptor implements Interceptor {
 private String dialect = ""; //数据库方言
?
 private Log log = LogFactory.getLog(PageInterceptor.class);
?
 @Override
 public Object intercept(Invocation invocation) throws Throwable {
 if(invocation.getTarget() instanceof RoutingStatementHandler){
 RoutingStatementHandler statementHandler = (RoutingStatementHandler)invocation.getTarget();
 StatementHandler delegate = (StatementHandler) CommonUtil.getFieldValue(statementHandler, "delegate");
 BoundSql boundSql = delegate.getBoundSql();
 Object obj = boundSql.getParameterObject();
 if (obj instanceof PageDto) {
 PageDto page = (PageDto) obj;
 //获取delegate父类BaseStatementHandler的mappedStatement属性
 MappedStatement mappedStatement = (MappedStatement)CommonUtil.getFieldValue(delegate, "mappedStatement");
 //拦截到的prepare方法参数是一个Connection对象
 Connection connection = (Connection)invocation.getArgs()[0];
 //获取当前要执行的Sql语句
 String sql = boundSql.getSql();
 //给当前的page参数对象设置总记录数
 this.setTotalRecord(page, mappedStatement, connection);
 //给当前的page参数对象补全完整信息
 //this.setPageInfo(page);
 //获取分页Sql语句
 String pageSql = this.getPageSql(page, sql);
 //设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
 CommonUtil.setFieldValue(boundSql, "sql", pageSql);
 }
 }
 return invocation.proceed();
 }
?
 /**
 * 给当前的参数对象page设置总记录数
 *
 * @param page Mapper映射语句对应的参数对象
 * @param mappedStatement Mapper映射语句
 * @param connection 当前的数据库连接
 */
 private void setTotalRecord(PageDto page, MappedStatement mappedStatement, Connection connection) throws Exception{
 //获取对应的BoundSql
 BoundSql boundSql = mappedStatement.getBoundSql(page);
 //获取对应的Sql语句
 String sql = boundSql.getSql();
 //获取计算总记录数的sql语句
 String countSql = this.getCountSql(sql);
 //通过BoundSql获取对应的参数映射
 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
 //利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。
 BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);
 //通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象
 ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql);
 //通过connection建立一个countSql对应的PreparedStatement对象。
 PreparedStatement pstmt = null;
 ResultSet rs = null;
 try {
 pstmt = connection.prepareStatement(countSql);
 //通过parameterHandler给PreparedStatement对象设置参数
 parameterHandler.setParameters(pstmt);
 //执行获取总记录数的Sql语句。
 rs = pstmt.executeQuery();
 if (rs.next()) {
 int totalRecord = rs.getInt(1);
 //给当前的参数page对象设置总记录数
 page.setTotalRecord(totalRecord);
 }
 } catch (SQLException e) {
 log.error(e);
 throw new SQLException();
 } finally {
 try {
 if (rs != null)
 rs.close();
 if (pstmt != null)
 pstmt.close();
 } catch (SQLException e) {
 log.error(e);
 throw new SQLException();
 }
 }
 }
?
 /**
 * 根据原Sql语句获取对应的查询总记录数的Sql语句
 * @param sql 原sql
 * @return 查询总记录数sql
 */
 private String getCountSql(String sql) {
 int index = new String(sql).toLowerCase().indexOf("from");
 return "select count(*) " + sql.substring(index);
 }
?
 /**
 * 给page对象补充完整信息
 *
 * @param page page对象
 */
 private void setPageInfo(PageDto page) {
 Integer totalRecord = page.getTotalRecord();
 Integer pageNo = page.getPageNo();
 Integer rows = page.getRows();
?
 //设置总页数
 Integer totalPage;
 if (totalRecord > rows) {
 if (totalRecord % rows == 0) {
 totalPage = totalRecord / rows;
 } else {
 totalPage = 1 + (totalRecord / rows);
 }
 } else {
 totalPage = 1;
 }
 page.setTotalPage(totalPage);
?
 //跳转页大于总页数时,默认跳转至最后一页
 if (pageNo > totalPage) {
 pageNo = totalPage;
 page.setPageNo(pageNo);
 }
?
 //设置是否有前页
 if(pageNo <= 1) {
 page.setHasPrevious(false);
 } else {
 page.setHasPrevious(true);
 }
?
 //设置是否有后页
 if(pageNo >= totalPage) {
 page.setHasNext(false);
 } else {
 page.setHasNext(true);
 }
 }
?
 /**
 * 根据page对象获取对应的分页查询Sql语句
 * 其它的数据库都 没有进行分页
 *
 * @param page 分页对象
 * @param sql 原sql语句
 * @return 分页sql
 */
 private String getPageSql(PageDto page, String sql) {
 StringBuffer sqlBuffer = new StringBuffer(sql);
 if ("mysql".equalsIgnoreCase(dialect)) {
 //int offset = (page.getPageNo() - 1) * page.getRows();
 sqlBuffer.append(" limit ").append(page.getOffset()).append(",").append(page.getRows());
 return sqlBuffer.toString();
 }
 return sqlBuffer.toString();
 }
?
 /**
 * 拦截器对应的封装原始对象的方法
 */
 @Override
 public Object plugin(Object arg0) {
?
 if (arg0 instanceof StatementHandler) {
 return Plugin.wrap(arg0, this);
 } else {
 return arg0;
 }
 }
?
 /**
 * 设置注册拦截器时设定的属性
 */
 @Override
 public void setProperties(Properties p) {
?
 }
?
 public String getDialect() {
 return dialect;
 }
?
 public void setDialect(String dialect) {
 this.dialect = dialect;
 }
?
}

重点讲解:

  1. @Intercept注解中的@Signature中标示的属性,标示当前拦截器要拦截的那个类的那个方法,拦截方法的传入的参数
  2. 首先要明白,Mybatis是对JDBC的一个高层次的封装。而JDBC在完成数据操作的时候必须要有一个陈述对象。而陈述对应的SQL语句是在是在陈之前产生的。所以我们的思路就是在生成报表之前对SQL进行下手。更改SQL语句成我们需要的!
  3. 对于MyBatis的,其声明的英文生成在RouteStatementHandler中。所以我们要做的就是拦截这个处理程序的prepare方法!然后修改的Sql语句!
@Override
 public Object intercept(Invocation invocation) throws Throwable {
 // 其实就是代理模式!
 RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
 StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate");
 String sql= delegate.getBoundSql().getSql();
 return invocation.proceed();
 }
  1. 我们知道利用Mybatis查询一个集合时传入Rowbounds对象即可指定其Offset和Limit,只不过其没有利用原生sql去查询罢了,我们现在做的,就是通过拦截器拿到这个参数,然后织入到SQL语句中,这样我们就可以完成一个物理分页!

注册拦截器

在Spring文件中引入拦截器

 <!-- MyBatis 分页拦截器-->
 <bean id="paginationInterceptor" class="xx.xx.interceptor.PageInterceptor">
 <property name="dialect" value="mysql"/>
 </bean>
 
 ...
 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 <property name="dataSource" ref="dataSource"/>
 <!-- 自动扫描mapping.xml文件 -->
 <property name="mapperLocations" value="classpath*:evileye/*.xml"/>
 <property name="plugins" ref="paginationInterceptor"/>
 </bean>

分页定义的接口:

 List<Student> selectForSearch(PageDto<Student> pageDto);

客户端调用如下:

 PageDto pageDto = new PageDto<>();
 Student student =new Student();
 student.setId(1234);
 student.setName("sky");
 pageDto.setSearchCondition(student);

相关推荐

程序员:JDK的安装与配置(完整版)_jdk的安装方法

对于Java程序员来说,jdk是必不陌生的一个词。但怎么安装配置jdk,对新手来说确实头疼的一件事情。我这里以jdk10为例,详细的说明讲解了jdk的安装和配置,如果有不明白的小伙伴可以评论区留言哦下...

Linux中安装jdk并配置环境变量_linux jdk安装教程及环境变量配置

一、通过连接工具登录到Linux(我这里使用的Centos7.6版本)服务器连接工具有很多我就不一一介绍了今天使用比较常用的XShell工具登录成功如下:二、上传jdk安装包到Linux服务器jdk...

麒麟系统安装JAVA JDK教程_麒麟系统配置jdk

检查检查系统是否自带java在麒麟系统桌面空白处,右键“在终端打开”,打开shell对话框输入:java–version查看是否自带java及版本如图所示,系统自带OpenJDK,要先卸载自带JDK...

学习笔记-Linux JDK - 安装&amp;配置

前提条件#检查是否存在JDKrpm-qa|grepjava#删除现存JDKyum-yremovejava*安装OracleJDK不分系统#进入安装文件目...

Linux新手入门系列:Linux下jdk安装配置

本系列文章是把作者刚接触和学习Linux时候的实操记录分享出来,内容主要包括Linux入门的一些理论概念知识、Web程序、mysql数据库的简单安装部署,希望能够帮到一些初学者,少走一些弯路。注意:L...

测试员必备:Linux下安装JDK 1.8你必须知道的那些事

1.简介在Oracle收购Sun后,Java的一系列产品就被整合到Oracle官网中,打开官网乍眼一看也不知道去哪里下载,还得一个一个的摸索尝试,而且网上大多数都是一些Oracle收购Sun前,或者就...

Linux 下安装JDK17_linux 安装jdk1.8 yum

一、安装环境操作系统:JDK版本:17二、安装步骤第一步:下载安装包下载Linux环境下的jdk1.8,请去官网(https://www.oracle.com/java/technologies/do...

在Ubuntu系统中安装JDK 17并配置环境变量教程

在Ubuntu系统上安装JDK17并配置环境变量是Java开发环境搭建的重要步骤。JDK17是Oracle提供的长期支持版本,广泛用于开发Java应用程序。以下是详细的步骤,帮助你在Ubuntu系...

如何在 Linux 上安装 Java_linux安装java的步骤

在桌面上拥抱Java应用程序,然后在所有桌面上运行它们。--SethKenlon(作者)无论你运行的是哪种操作系统,通常都有几种安装应用程序的方法。有时你可能会在应用程序商店中找到一个应用程序...

Windows和Linux环境下的JDK安装教程

JavaDevelopmentKit(简称JDK),是Java开发的核心工具包,提供了Java应用程序的编译、运行和开发所需的各类工具和类库。它包括了JRE(JavaRuntimeEnviro...

linux安装jdk_linux安装jdk软连接

JDK是啥就不用多介绍了哈,外行的人也不会进来看我的博文。依然记得读大学那会,第一次实验课就是在机房安装jdk,编写HelloWorld程序。时光飞逝啊,一下过了十多年了,挣了不少钱,买了跑车,娶了富...

linux安装jdk,全局配置,不同用户不同jdk

jdk1.8安装包链接:https://pan.baidu.com/s/14qBrh6ZpLK04QS8ogCepwg提取码:09zs上传文件解压tar-zxvfjdk-8u152-linux-...

运维大神教你在linux下安装jdk8_linux安装jdk1.7

1.到官网下载适合自己机器的版本。楼主下载的是jdk-8u66-linux-i586.tar.gzhttp://www.oracle.com/technetwork/java/javase/downl...

window和linux安装JDK1.8_linux 安装jdk1.8.tar

Windows安装JDK1.8的步骤:步骤1:下载JDK打开浏览器,找到JDK下载页面https://d.injdk.cn/download/oraclejdk/8在页面中找到并点击“下载...

最全的linux下安装JavaJDK的教程(图文详解)不会安装你来打我?

默认已经有了linux服务器,且有root账号首先检查一下是否已经安装过java的jdk任意位置输入命令:whichjava像我这个已经安装过了,就会提示在哪个位置,你的肯定是找不到。一般我们在...

取消回复欢迎 发表评论: